Skip to content

Latest commit

 

History

History
244 lines (215 loc) · 11.1 KB

File metadata and controls

244 lines (215 loc) · 11.1 KB

Generating Kind Code

Now that we have our kind and schema defined, we want to generate code from them that we can use. To do this, we need to add the kind to our manifest. The manifest is information about our app, including the versions and kinds it provides. If we don't add our kind to the manifest, it isn't considered part of our app.

Our project init created a default manifest for us, found in ./kinds/manifest.cue:

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: "issue-tracker-project"
	// 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: foo.ext.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: {
	    "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"]
			// }
		]
	}
}

// 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: []
    // [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.
    served: 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, <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
        }
    }
}

By default, the appName is the final part of our module path. We don't need to worry about extraPermissions, as our app doesn't need to work with any other apps' kinds. versions is pre-populated with a v1alpha1 version object, which has a kinds section. However, right now, the kinds list is empty. To add our kind to the v1alpha1 version in the manifest, we just need to add the field name of the v1alpha1 version of our kind:

kinds: [issuev1alpha1]

Tip

If you add a new kind to your project with the command grafana-app-sdk project kind add <KindName>, it will automatically add it to the manifest as well

In the future, we'll want to re-generate this code whenever we change anything in our kinds directory. The SDK provides a command for this: grafana-app-sdk generate, but our project init also gave us a make target which will do the same thing, so you can run either. Here, I'm running the make target:

make generate

This command should output a list of all the files it writes:

$ make generate
 * Writing file pkg/generated/issue/v1alpha1/constants.go
 * Writing file pkg/generated/issue/v1alpha1/issue_codec_gen.go
 * Writing file pkg/generated/issue/v1alpha1/issue_object_gen.go
 * Writing file pkg/generated/issue/v1alpha1/issue_schema_gen.go
 * Writing file pkg/generated/issue/v1alpha1/issue_spec_gen.go
 * Writing file pkg/generated/issue/v1alpha1/issue_status_gen.go
 * Writing file plugin/src/generated/issue/v1alpha1/issue_object_gen.ts
 * Writing file plugin/src/generated/issue/v1alpha1/types.metadata.gen.ts
 * Writing file plugin/src/generated/issue/v1alpha1/types.spec.gen.ts
 * Writing file plugin/src/generated/issue/v1alpha1/types.status.gen.ts
 * Writing file definitions/issue.issuetrackerproject.ext.grafana.com.json
 * Writing file definitions/issue-tracker-project-manifest.json
 * Writing file pkg/generated/manifestdata/issuetrackerproject_manifest.go

That's a bunch of files written! Let's tree the directory to understand the structure a bit better.

$ tree .
.
├── cmd
│   └── operator
├── definitions
│   ├── issue-tracker-project-manifest.json
│   └── issue.issuetrackerproject.ext.grafana.com.json
├── go.mod
├── go.sum
├── kinds
│   ├── cue.mod
│   │   └── module.cue
│   ├── issue.cue
│   └── manifest.cue
├── local
│   ├── additional
│   ├── config.yaml
│   ├── mounted-files
│   │   └── plugin
│   ├── scripts
│   │   ├── cluster.sh
│   │   └── push_image.sh
│   └── Tiltfile
├── Makefile
├── pkg
│   └── generated
│       ├── issue
│       │   └── v1alpha1
│       │       ├── constants.go
│       │       ├── issue_codec_gen.go
│       │       ├── issue_object_gen.go
│       │       ├── issue_schema_gen.go
│       │       ├── issue_spec_gen.go
│       │       └── issue_status_gen.go
│       ├── manifestdata
│       │   └── issuetrackerproject_manifest.go
└── plugin
    └── src
        └── generated
            └── issue
                └── v1alpha1
                    ├── issue_object_gen.ts
                    ├── types.metadata.gen.ts
                    ├── types.spec.gen.ts
                    └── types.status.gen.ts

20 directories, 23 files

All of our generated go code lives in pkg/generated, and all the generated TypeScript lives in plugin/src/generated. We also have some JSON files in definitions

Note that we also have generated TypeScript in our previously-empty plugin directory. By convention, the Grafana plugin for your project will live in the plugin directory, so here we've got some TypeScript generated in plugin/src/generated to use when we start working on the front-end of our plugin.

Generated Go Code

The package with the largest number of files generated by make generate is the pkg/generated/issue package. This is also the package where all of our generated go code lives (even with multiple kinds, all generated go code will live in pkg/generated).

Let's take a closer look at the list of files:

$ tree pkg/generated
pkg/generated
├── issue
│   └── v1alpha1
│       ├── constants.go
│       ├── issue_codec_gen.go
│       ├── issue_object_gen.go
│       ├── issue_schema_gen.go
│       ├── issue_spec_gen.go
│       └── issue_status_gen.go
├── manifestdata
    └── issuetrackerproject_manifest.go

3 directories, 7 files

The exported go types from our kind's v1alpha1 schema definition are issue_spec_gen.go and issue_status_gen.go. You'll note that issue_status_gen.go contain types and fields which we didn't define in our schema--that's because of the joined "default" status information. If we had defined a status or metadata in our schema, those fields would also be present in the generated types.

In addition to the types generated from our kind's schema, we have issue_object_gen.go, issue_schema_gen.go, and issue_codec_gen.go. issue_object_gen.go defines the complete object (with spec, status, and metadata) in a way that satisfies the resource.Object interface, so that it can be used with the SDK. Likewise, issue_schema_gen.go defines a resource.Schema for this specific version of the kind which can be used in your project, in addition to a resource.Kind for the kind. Finally, issue_codec_gen.go contains code for a kubernetes-JSON-bytes<->Issue Object codec, which is used by the Kind for marshaling and unmarshaling our Object when interacting with the API server.

Finally, we have issuetrackerproject_manifest.go in the pkg/generated/manifestdata directory. This contains an in-code version of our manifest. As we'll see a bit later, the manifest is also used in code to communicate app capabilities, so we have this in-code representation, as well as an API server one.

Generated TypeScript Code

$ tree plugin
plugin
└── src
    └── generated
        └── issue
            └── v1alpha1
                ├── issue_object_gen.ts
                ├── types.metadata.gen.ts
                ├── types.spec.gen.ts
                └── types.status.gen.ts

5 directories, 4 files

The generated TypeScript contains an interface built from our schema. Similarly to our go code, there are types for Spec and Status (and a legacy Metadata type), and an Object type which pulls them all together. TypeScript code is only generated for kinds where frontend: true.

Generated Custom Resource Definitions

Finally, we have the custom resource definition file that describes our issue kind as a CRD, which lives in definitions by default. Note that this is a CRD of the kind, not just the schema, so the CRD will contain all schema versions in the kind. This can be used to set up kubernetes as our storage layer for our project.

We also have a manifest here, which will be used by the grafana API server in the future to register the app.

$ tree definitions
definitions
├── issue-tracker-project-manifest.json
└── issue.issuetrackerproject.ext.grafana.com.json

1 directory, 2 files

So now we have a bunch of generated code, but we still need a project to actually use it in. The SDK gives us some tooling to set up our project with boilerplate code, so let's do that next.