cue-kind-definition

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

CUE Kind Definition

CUE Kind定义

Kinds are the schema definitions that drive the entire grafana-app-sdk code generation pipeline. Each kind describes a Kubernetes-style resource type: its name, versions, and per-version schema. All Go types, TypeScript types, API clients, CRD manifests, and the AppManifest are generated from these CUE files.
Kinds是驱动整个grafana-app-sdk代码生成流程的模式定义。每个Kind描述一种Kubernetes风格的资源类型:其名称、版本以及每个版本的模式。所有Go类型、TypeScript类型、API客户端、CRD清单和AppManifest均由这些CUE文件生成。

Adding a Kind

添加Kind

Use the CLI to scaffold a kind before editing:
bash
grafana-app-sdk project kind add <KindName> --overwrite
This creates a
.cue
file with scaffolding, field comments, and example values. Read the generated comments carefully — they explain every field's purpose.
Always use
--overwrite
when re-running to regenerate scaffolding without losing manual additions.
编辑前使用CLI搭建Kind的脚手架:
bash
grafana-app-sdk project kind add <KindName> --overwrite
这会创建一个包含脚手架、字段注释和示例值的
.cue
文件。请仔细阅读生成的注释——它们解释了每个字段的用途。
重新运行时请始终使用
--overwrite
参数,以在不丢失手动添加内容的情况下重新生成脚手架。

Kind File Structure

Kind文件结构

grafana-app-sdk project kind add
creates files directly in
kinds/
— the default layout is flat, all in
package kinds
:
kinds/
├── manifest.cue           # App manifest + version list declarations
├── mykind.cue             # Common (cross-version) kind metadata
└── mykind_v1alpha1.cue    # v1alpha1 schema + codegen config
For multi-version kinds the additional version files sit alongside:
kinds/
├── manifest.cue
├── mykind.cue
├── mykind_v1alpha1.cue
└── mykind_v1.cue
For larger, more complex kind definitions users may choose to organise kinds into per-kind and per-version subdirectories, each with their own package. The default CLI output uses the flat layout above.
grafana-app-sdk project kind add
会在
kinds/
目录下直接创建文件——默认布局为扁平结构,全部位于
package kinds
包中:
kinds/
├── manifest.cue           # App清单 + 版本列表声明
├── mykind.cue             # (跨版本)通用Kind元数据
└── mykind_v1alpha1.cue    # v1alpha1模式 + 代码生成配置
对于多版本Kind,额外的版本文件会放在同一目录下:
kinds/
├── manifest.cue
├── mykind.cue
├── mykind_v1alpha1.cue
└── mykind_v1.cue
对于更大、更复杂的Kind定义,用户可以选择将Kind组织到按Kind和版本划分的子目录中,每个子目录有自己的包。默认CLI输出使用上述扁平布局。

CUE Kind Anatomy

CUE Kind结构解析

A complete kind definition has three layers:
一个完整的Kind定义包含三层:

1. Common kind metadata (shared across versions)

1. 通用Kind元数据(所有版本共享)

cue
// kinds/mykind.cue
package kinds

myKind: {
    kind: "MyKind"               // Required: the kind name (PascalCase)
    // other cross-version fields (scope, pluralName, validation, mutation, conversion, etc.)
    // See references/kind-layout.md for the full field reference
}
cue
// kinds/mykind.cue
package kinds

myKind: {
    kind: "MyKind"               // 必填:Kind名称(大驼峰命名)
    // 其他跨版本字段(作用域、复数名称、验证、变更、转换等)
    // 完整字段参考请查看references/kind-layout.md
}

2. Per-version schema (one file per version)

2. 每个版本的模式(每个版本对应一个文件)

Each version joins the common metadata with its own schema via CUE's
&
operator:
cue
// kinds/mykind_v1alpha1.cue
package kinds

myKindv1alpha1: myKind & {
    // Version-specific schema
    schema: {
        // spec: desired state — set by users/clients, never by the operator
        spec: {
            title:       string
            description: string | *""     // optional with default
            count:       int & >=0
            enabled:     bool | *true
        }
        // status: observed state — written only by the operator/reconciler,
        // never by users. Mirrors Kubernetes spec/status conventions.
        status: {
            lastObservedGeneration: int | *0
            state:                  string | *""
            message:                string | *""
        }
    }

    // Code generation config
    codegen: {
        ts: { enabled: true }   // generate TypeScript types
        go: { enabled: true }   // generate Go types and client
    }
}
每个版本通过CUE的
&
运算符将通用元数据与自身模式合并:
cue
// kinds/mykind_v1alpha1.cue
package kinds

myKindv1alpha1: myKind & {
    // 版本特定模式
    schema: {
        // spec:期望状态 — 由用户/客户端设置,绝不会由运算符设置
        spec: {
            title:       string
            description: string | *""     // 可选字段,默认值为空
            count:       int & >=0
            enabled:     bool | *true
        }
        // status:观测状态 — 仅由运算符/协调器写入,
        // 用户不得写入。遵循Kubernetes的spec/status约定。
        status: {
            lastObservedGeneration: int | *0
            state:                  string | *""
            message:                string | *""
        }
    }

    // 代码生成配置
    codegen: {
        ts: { enabled: true }   // 生成TypeScript类型
        go: { enabled: true }   // 生成Go类型和客户端
    }
}

3. App manifest (version registration)

3. App清单(版本注册)

Since all files share
package kinds
, version objects are referenced directly — no imports needed in the flat layout:
cue
// kinds/manifest.cue
package kinds

App: {
    appName: "my-app"
    versions: {
        "v1alpha1": {
            schema: myKindv1alpha1
        }
    }
}
由于所有文件共享
package kinds
包,版本对象可直接引用——扁平布局中无需导入:
cue
// kinds/manifest.cue
package kinds

App: {
    appName: "my-app"
    versions: {
        "v1alpha1": {
            schema: myKindv1alpha1
        }
    }
}

spec vs status

spec vs status

This distinction follows Kubernetes conventions exactly:
spec
— desired state. Written by users and clients. The operator reads
spec
and works to make the world match it. Admission handlers validate and mutate
spec
. Never write to
spec
from a reconciler.
status
— observed state. Written only by the operator/reconciler after it has done work. Users and clients should treat
status
as read-only. Admission handlers must not modify
status
.
Typical
status
fields:
cue
status: {
    // Generation of the spec that was last successfully reconciled.
    // Set to metadata.generation after a successful reconcile loop.
    lastObservedGeneration: int | *0

    // Human-readable summary of current state
    state:   string | *""   // e.g. "Ready", "Provisioning", "Error"
    message: string | *""   // detail, especially on error

    // References to objects created by the reconciler.
    // e.g. the name of a ConfigMap or Deployment the reconciler provisioned.
    provisionedConfigMap: string | *""
    provisionedServiceAccount: string | *""
}
Fields that belong in
status
, not
spec
:
  • Anything the operator computes or creates (IDs, names, URLs of provisioned resources)
  • lastObservedGeneration
    /
    observedGeneration
  • conditions
    (Kubernetes-style condition arrays)
  • Current health or lifecycle state (
    "Ready"
    ,
    "Degraded"
    , etc.)
  • Timestamps of when the operator last acted
Fields that belong in
spec
, not
status
:
  • Everything the user configures as desired state
  • References to existing resources the user wants the app to interact with (the operator looks these up, it doesn't create them)
这种区分完全遵循Kubernetes约定:
spec
— 期望状态。由用户和客户端写入。运算符读取
spec
并努力使实际状态与期望状态匹配。准入控制器会验证并修改
spec
。绝不要从协调器写入
spec
status
— 观测状态。仅在运算符/协调器完成工作后写入。用户和客户端应将
status
视为只读。准入控制器不得修改
status
典型的
status
字段:
cue
status: {
    // 最后成功协调的spec版本。
    // 成功完成协调循环后设置为metadata.generation。
    lastObservedGeneration: int | *0

    // 当前状态的可读摘要
    state:   string | *""   // 例如:"Ready"、"Provisioning"、"Error"
    message: string | *""   // 详细信息,尤其是错误时

    // 协调器创建的对象引用。
    // 例如:协调器配置的ConfigMap或Deployment的名称。
    provisionedConfigMap: string | *""
    provisionedServiceAccount: string | *""
}
属于
status
而非
spec
的字段:
  • 运算符计算或创建的任何内容(配置资源的ID、名称、URL)
  • lastObservedGeneration
    /
    observedGeneration
  • conditions
    (Kubernetes风格的条件数组)
  • 当前健康或生命周期状态(
    "Ready"
    "Degraded"
    等)
  • 运算符上次操作的时间戳
属于
spec
而非
status
的字段:
  • 用户配置的所有期望状态内容
  • 用户希望应用与之交互的现有资源引用(运算符会查找这些资源,而非创建它们)

Type Definitions with
#

使用
#
进行类型定义

CUE supports named type definitions using the
#
prefix inside a
schema
block. Each
#Definition
generates a named Go struct and TypeScript interface alongside the kind's
Spec
type.
cue
schema: {
    #Threshold: {
        value:    float & >=0
        severity: "info" | "warning" | "critical"
        message:  string | *""
    }

    #ResourceRef: {
        name:      string & != ""
        namespace: string | *"default"
    }

    spec: {
        title:          string & != ""
        alertThreshold: #Threshold
        thresholds:     [...#Threshold]  // list of a defined type
        targetRef?:     #ResourceRef     // optional
    }
}
#
definitions are scoped to the
schema
block they are declared in.
Prefer
#
definitions when:
  • A struct is used in more than one field
  • A struct is large or complex enough that inlining hurts readability
  • A struct appears in a list (
    [...#MyType]
    )
Inline structs are fine when:
  • The struct is small and simple (2-3 fields) or shallow
  • It is used in only one place and unlikely to be reused
Maps (
{[string]: string}
) and lists of scalars (
[...string]
) are always fine inline.
CUE支持在
schema
块内使用
#
前缀进行命名类型定义。每个
#Definition
会与Kind的
Spec
类型一起生成命名Go结构体和TypeScript接口。
cue
schema: {
    #Threshold: {
        value:    float & >=0
        severity: "info" | "warning" | "critical"
        message:  string | *""
    }

    #ResourceRef: {
        name:      string & != ""
        namespace: string | *"default"
    }

    spec: {
        title:          string & != ""
        alertThreshold: #Threshold
        thresholds:     [...#Threshold]  // 定义类型的列表
        targetRef?:     #ResourceRef     // 可选字段
    }
}
#
定义的作用域限于其所在的
schema
块。
建议在以下场景使用
#
定义:
  • 结构体在多个字段中使用
  • 结构体较大或较复杂,内联会降低可读性
  • 结构体出现在列表中(
    [...#MyType]
以下场景适合内联结构体:
  • 结构体小而简单(2-3个字段)或层级浅
  • 仅在一个地方使用且不太可能复用
映射(
{[string]: string}
)和标量列表(
[...string]
)始终适合内联。

Schema Field Types

模式字段类型

CUE is a superset of JSON. Commonly used types and constraints:
cue
// Basic types
myString:  string
myInt:     int
myFloat:   float
myBool:    bool
myBytes:   bytes

// Optional with default
name: string | *"default-value"

// Constraints (using & to intersect)
port:     int & >=1 & <=65535
label:    string & =~"^[a-z][a-z0-9-]*$"  // regex constraint

// Enums (disjunctions)
status:   "pending" | "active" | "archived"

// Maps (always fine inline)
labels: {[string]: string}
attrs:  {[string]: _}

// Lists of scalars (fine inline)
tags: [...string]

// Optional field
description?: string
CUE是JSON的超集。常用类型和约束如下:
cue
// 基础类型
myString:  string
myInt:     int
myFloat:   float
myBool:    bool
myBytes:   bytes

// 可选字段带默认值
name: string | *"default-value"

// 约束(使用&进行交集)
port:     int & >=1 & <=65535
label:    string & =~"^[a-z][a-z0-9-]*$"  // 正则约束

// 枚举(析取)
status:   "pending" | "active" | "archived"

// 映射(始终适合内联)
labels: {[string]: string}
attrs:  {[string]: _}

// 标量列表(适合内联)
tags: [...string]

// 可选字段
description?: string

Custom Routes in CUE

CUE中的自定义路由

Routes can be defined at two levels. Both require corresponding Go handlers registered in
app.go
.
路由可在两个层级定义。两者都需要在
app.go
中注册对应的Go处理器。

Kind-level routes

Kind级路由

cue
MyKind: {
    kind: "MyKind"
    schema: { ... }

    routes: {
        "/actions/process": {
            "POST": {
                name: "processMyKind"  // unique within version; must start with a k8s verb
                request: {
                    body: {
                        reason: string
                    }
                }
                response: {
                    jobId:  string
                    status: string
                }
            }
        }
    }
}
cue
MyKind: {
    kind: "MyKind"
    schema: { ... }

    routes: {
        "/actions/process": {
            "POST": {
                name: "processMyKind"  // 版本内唯一;必须以k8s动词开头
                request: {
                    body: {
                        reason: string
                    }
                }
                response: {
                    jobId:  string
                    status: string
                }
            }
        }
    }
}

Version-level routes

版本级路由

cue
versions: {
    "v1alpha1": {
        routes: {
            namespaced: {
                "/summary": {
                    "GET": {
                        name: "getNamespacedSummary"
                        response: { count: int }
                    }
                }
            }
            cluster: {
                "/health": {
                    "GET": {
                        name: "getHealth"
                        response: { status: string }
                    }
                }
            }
        }
    }
}
After adding routes, run
grafana-app-sdk generate
— routes are included in the AppManifest and
ValidateManifest
will fail if a handler is missing.
cue
versions: {
    "v1alpha1": {
        routes: {
            namespaced: {
                "/summary": {
                    "GET": {
                        name: "getNamespacedSummary"
                        response: { count: int }
                    }
                }
            }
            cluster: {
                "/health": {
                    "GET": {
                        name: "getHealth"
                        response: { status: string }
                    }
                }
            }
        }
    }
}
添加路由后,运行
grafana-app-sdk generate
——路由会被包含在AppManifest中,如果缺少处理器,
ValidateManifest
会失败。

Version Compatibility Rules

版本兼容性规则

When a kind has multiple versions, fields declared in the common metadata object must match across all versions. Schema fields (inside
schema.spec
) can differ per version, but:
  • The
    kind
    field must be identical in every version
  • Breaking changes (removing fields, changing types, adding required fields) must be introduced via a new version — never by modifying a stable version (
    v1
    ,
    v2
    )
  • Use
    status
    for server-managed fields; never put mutable server state in
    spec
当一个Kind有多个版本时,通用元数据对象中声明的字段必须在所有版本中保持一致。模式字段(
schema.spec
内的字段)可以因版本而异,但需遵循:
  • kind
    字段在每个版本中必须完全相同
  • 破坏性变更(删除字段、更改类型、添加必填字段)必须通过新版本引入——绝不要修改稳定版本(
    v1
    v2
  • 使用
    status
    存储服务器管理的字段;绝不要将可变服务器状态放在
    spec

Codegen Configuration

代码生成配置

Control what gets generated per kind per version:
cue
codegen: {
    ts: { enabled: true | false }   // TypeScript types
    go: { enabled: true | false }   // Go types + client
}
Disabling
go
for frontend-only apps avoids generating unused Go code. Disabling
ts
for backend-only resources reduces TypeScript bundle size. Both default to
true
when omitted.
控制每个Kind每个版本生成的内容:
cue
codegen: {
    ts: { enabled: true | false }   // TypeScript类型
    go: { enabled: true | false }   // Go类型 + 客户端
}
对于仅前端应用,禁用
go
可避免生成未使用的Go代码。对于仅后端资源,禁用
ts
可减小TypeScript包体积。省略时两者默认均为
true

After Editing Kinds

编辑Kind之后

Always run generate after any change to
.cue
files:
bash
grafana-app-sdk generate
The generated files in
pkg/generated/
must never be edited manually — they are overwritten on every generate run.
修改
.cue
文件后请始终运行生成命令:
bash
grafana-app-sdk generate
pkg/generated/
目录下的生成文件绝不能手动编辑——每次运行生成命令时都会被覆盖。

Resources

资源