Rules for writing and maintaining standalone example binaries in examples/.
| Subdirectory | Purpose |
|---|---|
grpc/ |
gRPC client usage -- ClientBuilder auth modes, inventory operations |
rbac/ |
REST workspace API helpers (FetchDefaultWorkspace, ListWorkspaces) |
console/ |
Console identity helpers (PrincipalFromRHIdentity) |
- Every example file is
package mainwith its ownmain()function. - One file per example -- no multi-file packages within a subdirectory.
- File naming:
snake_case.gomatching the operation (e.g.,report_resource.go,check_bulk.go). - The
main()function delegates to a named function:func main() { reportResource() }.
Examples read configuration from environment variables via os.Getenv. See the environment variables table in AGENTS.md for the full list. The .env.sample file at the repo root defines all available variables.
Examples that read env vars typically import _ "github.com/joho/godotenv/autoload" for automatic .env loading. Place this import in a separate block (blank-line-separated) from other imports. Some examples (e.g., authenticated.go, insecure.go, check_bulk.go) omit this import because they are designed to work without a .env file or rely on environment variables being set externally -- follow the pattern of whichever auth mode you are demonstrating.
The .env file is gitignored. Running make build auto-copies .env.sample to .env if it does not exist.
When importing versioned SDK packages, use consistent aliasing:
v2 "github.com/project-kessel/kessel-sdk-go/kessel/rbac/v2"
kesselgrpc "github.com/project-kessel/kessel-sdk-go/kessel/grpc"The kesselgrpc alias avoids collision with google.golang.org/grpc. For kessel/inventory/v1beta2, examples currently import directly without an alias, though SDK code uses v1beta2 as the alias.
Four cases of error handling are used in examples, depending on what failed:
- OIDC discovery errors: Use
panic(err). Discovery failure means the environment is misconfigured and there is nothing to clean up. - ClientBuilder errors: Use
log.Fatal("Failed to create gRPC client:", err). - gRPC call errors: Check
status.FromError(err)and switch on gRPC status codes (codes.Unavailable,codes.PermissionDenied, default). Uselog.Fatalfor each branch. - Console/utility errors: Use
panic(err)for identity parsing failures.
Do not use fmt.Println for errors. Do not silently ignore errors.
Always defer conn.Close() immediately after a successful Build() call. Log close errors instead of discarding them:
defer func() {
if closeErr := conn.Close(); closeErr != nil {
log.Printf("Failed to close gRPC client: %v", closeErr)
}
}()Every gRPC example in the repo uses this exact pattern. Do not use a bare defer conn.Close().
All gRPC examples follow this three-phase structure:
- Setup: Construct credentials (if authenticated), build the client via
ClientBuilder, defer connection close. - Execute: Build the request struct, call the API method.
- Output: Print the response with
fmt.Printf("... response: %+v\n", response).
addr[T any](t T) *T-- a generic helper for creating pointers to literals (seereport_resource.go). Define it locally in the file that needs it; do not create a shared utils file.- RBAC utility constructors (
v2.PrincipalSubject,v2.WorkspaceResource) -- use these instead of buildingResourceReferenceandSubjectReferencestructs manually when the resource type is an RBAC concept.
- Create a single
.gofile in the appropriate subdirectory (grpc/,rbac/, orconsole/). - Add a
go build -o bin/<output-name> ./examples/<subdir>/<file>.goline to thebuildtarget in the rootMakefile. - Run
make buildto verify the example compiles. - If creating a new subdirectory, the CI lint workflow (
examples/*/*.goglob) picks it up automatically, but the Makefilebuildtarget needs an explicit entry.
- Examples are not tests. They require a live Kessel server and are never run by
make test. - Examples are build-checked only (
make buildand CI lint). - Do not add
_test.gofiles underexamples/.
Use v1beta2 for all new examples. See AGENTS.md -- API Version to Use for details.