From 7aed0da22c916656e8e6cdc8e376da0b94ecc734 Mon Sep 17 00:00:00 2001 From: Igor Suleymanov Date: Wed, 24 Sep 2025 15:37:57 +0300 Subject: [PATCH] Add a CI step for checking app SDK codegen status (#111528) * Add a CI step for checking app SDK codegen status What This commit adds a CI step for checking the status of code generated with Grafana App SDK. The step fails if there is a git diff as a result of the codegen step. It also updates generated code to make sure we're starting from a correct state. Why This ensures that when the schemas or the SDK version are updated, the codegen mismatch is caught early at the PR stage. Signed-off-by: Igor Suleymanov * Format generated code Signed-off-by: Igor Suleymanov --------- Signed-off-by: Igor Suleymanov --- .github/workflows/backend-code-checks.yml | 1 + Makefile | 16 +++++++-- .../v0alpha1/dashboard_client_gen.go | 4 ++- .../dashboard/v1beta1/dashboard_client_gen.go | 4 ++- .../v2alpha1/dashboard_client_gen.go | 4 ++- .../dashboard/v2beta1/dashboard_client_gen.go | 4 ++- apps/dashboard/pkg/apis/dashboard_manifest.go | 35 +++++++++++++++++++ .../apis/folder/v1beta1/folder_client_gen.go | 4 ++- apps/folder/pkg/apis/folder_manifest.go | 23 ++++++++++++ 9 files changed, 88 insertions(+), 7 deletions(-) diff --git a/.github/workflows/backend-code-checks.yml b/.github/workflows/backend-code-checks.yml index 043b9bec747..0b4fece91ab 100644 --- a/.github/workflows/backend-code-checks.yml +++ b/.github/workflows/backend-code-checks.yml @@ -58,6 +58,7 @@ jobs: run: | CODEGEN_VERIFY=1 make gen-cue CODEGEN_VERIFY=1 make gen-jsonnet + CODEGEN_VERIFY=1 make gen-apps - name: Validate go.mod run: go run scripts/modowners/modowners.go check go.mod diff --git a/Makefile b/Makefile index c4ffd775e54..b6ea79ea13e 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,19 @@ gen-cuev2: ## Do all CUE code generation APPS_DIRS := ./apps/dashboard ./apps/folder ./apps/alerting/notifications .PHONY: gen-apps -gen-apps: ## Generate code for Grafana App SDK apps +gen-apps: do-gen-apps gofmt ## Generate code for Grafana App SDK apps and run gofmt + @if [ -n "$$CODEGEN_VERIFY" ]; then \ + echo "Verifying generated code is up to date..."; \ + if ! git diff --quiet; then \ + echo "Error: Generated apps code is not up to date. Please run 'make gen-apps' to regenerate."; \ + git diff --name-only; \ + exit 1; \ + fi; \ + echo "Generated apps code is up to date."; \ + fi + +.PHONY: do-gen-apps +do-gen-apps: ## Generate code for Grafana App SDK apps for dir in $(APPS_DIRS); do \ $(MAKE) -C $$dir generate; \ done @@ -388,7 +400,7 @@ lint-go-diff: .PHONY: gofmt gofmt: ## Run gofmt for all Go files. - gofmt -s -w . + @go list -m -f '{{.Dir}}' | xargs -I{} sh -c 'test ! -f {}/.nolint && echo {}' | xargs gofmt -s -w 2>&1 | grep -v '/pkg/build/' || true # with disabled SC1071 we are ignored some TCL,Expect `/usr/bin/env expect` scripts .PHONY: shellcheck diff --git a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go index f20c92ae314..08e772728d4 100644 --- a/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v0alpha1/dashboard_client_gen.go @@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif return c.client.Patch(ctx, identifier, req, opts) } -func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { +func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { return c.client.Update(ctx, &Dashboard{ TypeMeta: metav1.TypeMeta{ Kind: DashboardKind().Kind(), @@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS }, ObjectMeta: metav1.ObjectMeta{ ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, }, Status: newStatus, }, resource.UpdateOptions{ diff --git a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go index 5ab0ff632a2..41bd34c838f 100644 --- a/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v1beta1/dashboard_client_gen.go @@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif return c.client.Patch(ctx, identifier, req, opts) } -func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { +func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { return c.client.Update(ctx, &Dashboard{ TypeMeta: metav1.TypeMeta{ Kind: DashboardKind().Kind(), @@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS }, ObjectMeta: metav1.ObjectMeta{ ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, }, Status: newStatus, }, resource.UpdateOptions{ diff --git a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go index 587ff5e586b..a18e1438711 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2alpha1/dashboard_client_gen.go @@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif return c.client.Patch(ctx, identifier, req, opts) } -func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { +func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { return c.client.Update(ctx, &Dashboard{ TypeMeta: metav1.TypeMeta{ Kind: DashboardKind().Kind(), @@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS }, ObjectMeta: metav1.ObjectMeta{ ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, }, Status: newStatus, }, resource.UpdateOptions{ diff --git a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go index 9fc3441b2ea..5e419ee2861 100644 --- a/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go +++ b/apps/dashboard/pkg/apis/dashboard/v2beta1/dashboard_client_gen.go @@ -76,7 +76,7 @@ func (c *DashboardClient) Patch(ctx context.Context, identifier resource.Identif return c.client.Patch(ctx, identifier, req, opts) } -func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { +func (c *DashboardClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus DashboardStatus, opts resource.UpdateOptions) (*Dashboard, error) { return c.client.Update(ctx, &Dashboard{ TypeMeta: metav1.TypeMeta{ Kind: DashboardKind().Kind(), @@ -84,6 +84,8 @@ func (c *DashboardClient) UpdateStatus(ctx context.Context, newStatus DashboardS }, ObjectMeta: metav1.ObjectMeta{ ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, }, Status: newStatus, }, resource.UpdateOptions{ diff --git a/apps/dashboard/pkg/apis/dashboard_manifest.go b/apps/dashboard/pkg/apis/dashboard_manifest.go index 048e7ca7d53..1e8f261e65b 100644 --- a/apps/dashboard/pkg/apis/dashboard_manifest.go +++ b/apps/dashboard/pkg/apis/dashboard_manifest.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kube-openapi/pkg/spec3" v0alpha1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1" v1beta1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1" @@ -34,6 +35,10 @@ var appManifestData = app.ManifestData{ Conversion: false, }, }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + }, }, { @@ -47,6 +52,10 @@ var appManifestData = app.ManifestData{ Conversion: false, }, }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + }, }, { @@ -60,6 +69,10 @@ var appManifestData = app.ManifestData{ Conversion: false, }, }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + }, }, { @@ -73,6 +86,10 @@ var appManifestData = app.ManifestData{ Conversion: false, }, }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + }, }, }, } @@ -104,6 +121,7 @@ var customRouteToGoResponseType = map[string]any{} // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. // kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. // If there is no association for the provided kind, version, custom route path, and method, exists will return false. +// Resource routes (those without a kind) should prefix their route with "/" if the route is namespaced (otherwise the route is assumed to be cluster-scope) func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { if len(path) > 0 && path[0] == '/' { path = path[1:] @@ -122,8 +140,22 @@ func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goTyp return goType, exists } +var customRouteToGoRequestBodyType = map[string]any{} + +func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + type GoTypeAssociator struct{} +func NewGoTypeAssociator() *GoTypeAssociator { + return &GoTypeAssociator{} +} + func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) { return ManifestGoTypeAssociator(kind, version) } @@ -133,3 +165,6 @@ func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb str func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) { return ManifestCustomRouteQueryAssociator(kind, version, path, verb) } +func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb) +} diff --git a/apps/folder/pkg/apis/folder/v1beta1/folder_client_gen.go b/apps/folder/pkg/apis/folder/v1beta1/folder_client_gen.go index 79d2eeca53a..6923c21d27c 100644 --- a/apps/folder/pkg/apis/folder/v1beta1/folder_client_gen.go +++ b/apps/folder/pkg/apis/folder/v1beta1/folder_client_gen.go @@ -76,7 +76,7 @@ func (c *FolderClient) Patch(ctx context.Context, identifier resource.Identifier return c.client.Patch(ctx, identifier, req, opts) } -func (c *FolderClient) UpdateStatus(ctx context.Context, newStatus FolderStatus, opts resource.UpdateOptions) (*Folder, error) { +func (c *FolderClient) UpdateStatus(ctx context.Context, identifier resource.Identifier, newStatus FolderStatus, opts resource.UpdateOptions) (*Folder, error) { return c.client.Update(ctx, &Folder{ TypeMeta: metav1.TypeMeta{ Kind: FolderKind().Kind(), @@ -84,6 +84,8 @@ func (c *FolderClient) UpdateStatus(ctx context.Context, newStatus FolderStatus, }, ObjectMeta: metav1.ObjectMeta{ ResourceVersion: opts.ResourceVersion, + Namespace: identifier.Namespace, + Name: identifier.Name, }, Status: newStatus, }, resource.UpdateOptions{ diff --git a/apps/folder/pkg/apis/folder_manifest.go b/apps/folder/pkg/apis/folder_manifest.go index f2c936c9686..37e994e62c6 100644 --- a/apps/folder/pkg/apis/folder_manifest.go +++ b/apps/folder/pkg/apis/folder_manifest.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana-app-sdk/app" "github.com/grafana/grafana-app-sdk/resource" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kube-openapi/pkg/spec3" v1beta1 "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1" ) @@ -31,6 +32,10 @@ var appManifestData = app.ManifestData{ Conversion: false, }, }, + Routes: app.ManifestVersionRoutes{ + Namespaced: map[string]spec3.PathProps{}, + Cluster: map[string]spec3.PathProps{}, + }, }, }, } @@ -59,6 +64,7 @@ var customRouteToGoResponseType = map[string]any{} // ManifestCustomRouteResponsesAssociator returns the associated response go type for a given kind, version, custom route path, and method, if one exists. // kind may be empty for custom routes which are not kind subroutes. Leading slashes are removed from subroute paths. // If there is no association for the provided kind, version, custom route path, and method, exists will return false. +// Resource routes (those without a kind) should prefix their route with "/" if the route is namespaced (otherwise the route is assumed to be cluster-scope) func ManifestCustomRouteResponsesAssociator(kind, version, path, verb string) (goType any, exists bool) { if len(path) > 0 && path[0] == '/' { path = path[1:] @@ -77,8 +83,22 @@ func ManifestCustomRouteQueryAssociator(kind, version, path, verb string) (goTyp return goType, exists } +var customRouteToGoRequestBodyType = map[string]any{} + +func ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb string) (goType any, exists bool) { + if len(path) > 0 && path[0] == '/' { + path = path[1:] + } + goType, exists = customRouteToGoRequestBodyType[fmt.Sprintf("%s|%s|%s|%s", version, kind, path, strings.ToUpper(verb))] + return goType, exists +} + type GoTypeAssociator struct{} +func NewGoTypeAssociator() *GoTypeAssociator { + return &GoTypeAssociator{} +} + func (g *GoTypeAssociator) KindToGoType(kind, version string) (goType resource.Kind, exists bool) { return ManifestGoTypeAssociator(kind, version) } @@ -88,3 +108,6 @@ func (g *GoTypeAssociator) CustomRouteReturnGoType(kind, version, path, verb str func (g *GoTypeAssociator) CustomRouteQueryGoType(kind, version, path, verb string) (goType runtime.Object, exists bool) { return ManifestCustomRouteQueryAssociator(kind, version, path, verb) } +func (g *GoTypeAssociator) CustomRouteRequestBodyGoType(kind, version, path, verb string) (goType any, exists bool) { + return ManifestCustomRouteRequestBodyAssociator(kind, version, path, verb) +}