Apps: Add Example App to ./apps (#112069)

* [API Server] Add Example App for reference use.

* Remove Printlns.

* Upgrade app-sdk to v0.46.0, update apps to handle breaking changes.

* Only start the reconciler for the example app if the v1alpha1 API version is enabled.

* Some comment doc updates.

* Run make update-workspace

* Set codeowner for /apps/example

* Run make gofmt and make update-workspace

* Run prettier on apps/example/README.md

* Add COPY apps/example to Dockerfile

* Add an authorizer to the example app.

* Fix import ordering.

* Update apps/example/kinds/manifest.cue

Co-authored-by: Owen Diehl <ow.diehl@gmail.com>

* Run make update-workspace

* Re-run make gen-go for enterprise import updates

* Run make update-workspace

---------

Co-authored-by: Owen Diehl <ow.diehl@gmail.com>
This commit is contained in:
Austin Pond
2025-10-27 12:01:10 -04:00
committed by GitHub
co-authored by Owen Diehl
parent d25f5199c7
commit bf65c43783
71 changed files with 3744 additions and 4 deletions
+2
View File
@@ -0,0 +1,2 @@
module: "github.com/grafana/grafana/apps/example/kinds"
language: version: "v0.8.2"
+52
View File
@@ -0,0 +1,52 @@
package kinds
// This is our ExampleKind definition, which contains kind metadata. It is the same across all versions of the kind.
exampleKind: {
// Name is the human-readable name which is used for generated type names.
kind: "Example"
// Scope determines the scope of the kind in the API server. It currently allows two values:
// * Namespaced - resources for this kind are created inside namespaces
// * Cluster - resource for this kind are always cluster-wide (this can be thought of as a "global" namespace)
// If not present, this defaults to "Namespaced"
scope: "Namespaced"
// [OPTIONAL]
// The human-readable plural form of the "name" field.
// Will default to <name>+"s" if not present.
pluralName: "Examples"
validation: {
operations: [
"CREATE",
"UPDATE",
]
}
mutation: {
operations: [
"CREATE",
"UPDATE",
]
}
conversion: true
// [OPTIONAL]
// Codegen is a trait that tells the grafana-app-sdk, or other code generation tooling, how to process this kind.
// If not present, default values within the codegen trait are used.
// If you wish to specify codegen per-version, put this section in the version's object
// (for example, exampleKindv1alpha1) instead.
codegen: {
// [OPTIONAL]
// ts contains TypeScript code generation properties for the kind
ts: {
// [OPTIONAL]
// enabled indicates whether the CLI should generate front-end TypeScript code for the kind.
// Defaults to true if not present.
enabled: true
}
// [OPTIONAL]
// go contains go code generation properties for the kind
go: {
// [OPTIONAL]
// enabled indicates whether the CLI should generate back-end go code for the kind.
// Defaults to true if not present.
enabled: true
}
}
}
+15
View File
@@ -0,0 +1,15 @@
package kinds
// This is the v0alpha1 version of the kind. Please see v1alpha1 for more complete comments
// and a more complex schema and set of capabilities.
examplev0alpha1: exampleKind & {
schema: {
// Spec is the schema of our resource. The spec should include all the user-editable information for the kind.
spec: {
firstField: int
}
status: {
lastObservedGeneration: int64
}
}
}
+76
View File
@@ -0,0 +1,76 @@
package kinds
// This is the v1alpha1 version of the kind, which joins the kind metadata and
// version-specific information for the kind, such as the schema
examplev1alpha1: exampleKind & {
// schema is the schema for this version of the kind
// As an API server-expressable resource, the schema has a restricted format:
// {
// spec: { ... }
// status: { ... } // optional
// metadata: { ... } // optional
// }
// `spec` must always be present, and is the schema for the object.
// `status` is optional, and should contain status or state information which is typically not user-editable
// (controlled by controllers/operators). The kind system adds some implicit status information which is
// common across all kinds, and becomes present in the unified lineage used for code generation and other tooling.
// `metadata` is optional, and should contain kind- or schema-specific metadata. The kind system adds
// an explicit set of common metadata which can be found in the definition file for a CUE kind at
// [https://github.com/grafana/grafana-app-sdk/blob/main/codegen/cuekind/def.cue]
// additional metadata fields cannot conflict with the common metadata field names
schema: {
// #DefinedType is a re-usable definition for us to use in our schema.
// Fields leading with # are definitions in CUE and won't be included in the generated types.
#DefinedType: {
// Info is information about this entry. This comment, like all comments
// on fields or definitions, will be copied into the generated types as well.
info: string
// Next is an optional next element in the DefinedType, allowing for a self-referential
// linked-list like structure. The ? in the field makes this optional.
next?: #DefinedType
}
// Spec is the schema of our resource. The spec should include all the user-editable information for the kind.
spec: {
// Example fields
firstField: string
secondField: int
list?: #DefinedType
}
// status is where state and status information which may be used or updated by the operator or back-end should be placed
// If you do not have any such information, you do not need to include this field,
// however, as mentioned above, certain fields will be added by the kind system regardless.
status: {
lastObservedGeneration: int64
}
// Custom is a subresource that will be stored the same way status is stored,
// and requires using the /custom route to update.
// Its content is returned as part of a GET to the resource itself, just like with status.
// To route a subresource to an arbitrary handler, use the 'routes' field instead (see below).
custom: {
myField: string
otherField: string
}
// metadata if where kind- and schema-specific metadata goes. This is converted into typed annotations
// with getters and setters by the code generation.
//metadata: {
// kindSpecificField: string
//}
}
// routes contains subresource routes for the kind, which are exposed as HTTP handlers on 'examples/<resource name>/<subresource>'.
// This allows you to add additional non-storage-based handlers to your kind.
// These should only be used if the behavior cannot be accomplished by reconciliation on storage events.
routes: {
// This will add a handler for /foo on the resource
"foo": {
// GET request handler. A subresource route can have multiple methods attached to it.
// Allowed values are GET, POST, PUT, DELETE, PATCH, HEAD, and OPTIONS
"GET": {
// The response type for the GET /foo method. This will generate a go type, and will also be used for the OpenAPI definition for the route.
response: {
message: string
}
}
}
}
}
+116
View File
@@ -0,0 +1,116 @@
package kinds
manifest: {
// appName is the unique name of your app. It is used to reference the app from other config objects,
// and to generate the group used by your app in the app platform API.
appName: "example"
// groupOverride can be used to specify a non-appName-based API group.
// By default, an app's API group is LOWER(REPLACE(appName, '-', '')).ext.grafana.com,
// but there are cases where this needs to be changed.
// Keep in mind that changing this after an app is deployed can cause problems with clients and/or kind data.
groupOverride: "example.grafana.app"
// versions is a map of versions supported by your app. Version names should follow the format "v<integer>" or
// "v<integer>(alpha|beta)<integer>". Each version contains the kinds your app manages for that version.
// If your app needs access to kinds managed by another app, use permissions.accessKinds to allow your app access.
versions: {
"v0alpha1": v0alpha1
"v1alpha1": v1alpha1
}
// extraPermissions contains any additional permissions your app may require to function.
// Your app will always have all permissions for each kind it manages (the items defined in 'kinds').
extraPermissions: {
// If your app needs access to additional kinds supplied by other apps, you can list them here
accessKinds: [
// Here is an example for your app accessing the playlist kind for reads and watch
// {
// group: "playlist.grafana.app"
// resource: "playlists"
// actions: ["get","list","watch"]
// }
]
}
}
v0alpha1: {
kinds: [examplev0alpha1]
// This is explicitly set to false to keep the example app disabled by default.
// It can be enabled via conf overrides, or by setting this value to true and regenerating.
served: false
}
// v1alpha1 is the v1alpha1 version of the app's API.
// It includes kinds which the v1alpha1 API serves, and (future) custom routes served globally from the v1alpha1 version.
v1alpha1: {
// kinds is the list of kinds served by this version
kinds:[examplev1alpha1]
// [OPTIONAL]
// served indicates whether this particular version is served by the API server.
// served should be set to false before a version is removed from the manifest entirely.
// served defaults to true if not present.
// This is explicitly set to false to keep the example app disabled by default.
// It can be enabled via conf overrides, or by setting this value to true and regenerating.
served: false
// routes contains resource routes for the version, which are split into 'namespaced' and 'cluster' scoped routes.
// This allows you to add additional non-storage- and non-kind- based handlers for your app.
// These should only be used if the behavior cannot be accomplished by reconciliation on storage events or subresource routes on a kind.
routes: {
// namespaced contains namespace-scoped resource routes for the version,
// which are exposed as HTTP handlers on '<version>/namespaces/<namespace>/<route>'.
namespaced: {
"/something": {
"GET": {
response: {
namespace: string
message: string
}
request: {
query: {
message?: string
}
}
}
}
}
// cluster contains cluster-scoped resource routes for the version,
// which are exposed as HTTP handlers on '<version>/<route>'.
cluster: {
"/other": {
"GET": {
response: {
message: string
}
request: {
query: {
message?: string
}
}
responseMetadata: typeMeta: false // Don't generate or return kubernetes type metadata for this object
}
}
}
}
// [OPTIONAL]
// Codegen is a trait that tells the grafana-app-sdk, or other code generation tooling, how to process this kind.
// If not present, default values within the codegen trait are used.
// If you wish to specify codegen per-version, put this section in the version's object
// (for example, <no value>v1alpha1) instead.
codegen: {
// [OPTIONAL]
// ts contains TypeScript code generation properties for the kind
ts: {
// [OPTIONAL]
// enabled indicates whether the CLI should generate front-end TypeScript code for the kind.
// Defaults to true if not present.
enabled: true
}
// [OPTIONAL]
// go contains go code generation properties for the kind
go: {
// [OPTIONAL]
// enabled indicates whether the CLI should generate back-end go code for the kind.
// Defaults to true if not present.
enabled: true
}
}
}