关于 admission-control
Admission Control 介绍如何为 grafana-app-sdk 管理的资源实现校验和变更处理器,面向需要在持久化前拒绝请求或调整对象的应用开发者。独立运行与 grafana/apps 运行时使用相同的处理器逻辑。
主要功能
- 01校验创建和更新请求
- 02在持久化前变更资源
- 03读取请求对象及操作详情
- 04为受管类型注册处理器
- 05支持独立和 grafana/apps 运行时
适用场景
- 01拒绝无效的资源请求
- 02设置默认值或规范化资源字段
- 03在更新时实施不可变性规则
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ grafana/ skills --skill admission-control安装后在 Claude Code 中输入/admission-control确认已加载。
附带文件references/admission-patterns.md
详细介绍
准入控制
准入控制会在资源创建或更新请求持久化之前拦截这些请求。在 grafana-app-sdk 中有两种类型:
- 验证 — 接受或拒绝请求;不能修改资源
- 变更 — 在资源持久化之前修改资源(例如,设置默认值、规范化字段)
无论应用作为独立 Operator 运行,还是在 grafana/apps 中运行,其准入业务逻辑都相同。唯一的区别是运行时:独立应用会启动自己的 Webhook 服务;grafana/apps 应用则会将准入处理程序自动注册为 Kubernetes 插件。
获取存根
对于独立应用,如果 pkg/app/app.go 尚不存在,可以生成一个 App 存根:
grafana-app-sdk project component add operator
这会创建脚手架 simple.App,可以为 ManagedKinds 中的每种类型添加准入处理程序。
验证器接口
// Implement this interface for each kind you want to validate
type Validator interface {
Validate(ctx context.Context, request *app.AdmissionRequest) error
}
- 返回
nil以准入请求 - 返回错误以拒绝请求(错误消息会返回给 API 调用方)
app.AdmissionRequest可用于访问传入对象和操作类型- 可以使用
k8s.NewAdmissionError(err error, statusCode int, reason string)(来自"github.com/grafana/grafana-app-sdk/k8s")更好地控制返回的错误信息
验证器示例
type MyKindValidator struct{}
func (v *MyKindValidator) Validate(ctx context.Context, req *app.AdmissionRequest) error {
obj, ok := req.Object.(*v1.MyKind)
if !ok {
return fmt.Errorf("admission request object was of invalid type %T (expected *v1.MyKind)", req.Object)
}
// Validate spec fields
if obj.Spec.Title == "" {
return fmt.Errorf("spec.title is required")
}
if obj.Spec.Count < 0 {
return fmt.Errorf("spec.count must be non-negative, got %d", obj.Spec.Count)
}
// Distinguish create vs update
if req.Action == resource.AdmissionActionUpdate && req.OldObject != nil {
old, ok := req.OldObject.(*v1.MyKind)
if !ok {
return fmt.Errorf("admission request old object was of invalid type %T (expected *v1.MyKind)", req.OldObject)
}
if old.Spec.Title != obj.Spec.Title {
return fmt.Errorf("spec.title is immutable after creation")
}
}
return nil
}
变更型准入(变更器)
// Implement this interface to mutate resources before persistence
type Mutator interface {
Mutate(ctx context.Context, request *app.AdmissionRequest) (*app.MutatingResponse, error)
}
- 返回包含(可选修改后的)对象的
MutatingResponse - 返回错误以完全拒绝请求
- 最佳实践是通过验证器拒绝请求,而不是通过变更器
变更处理程序示例
type MyKindMutator struct{}
func (m *MyKindMutator) Mutate(
ctx context.Context,
req *app.AdmissionRequest,
) (*app.MutatingResponse, error) {
obj, ok := req.Object.(*v1.MyKind)
if !ok {
return nil, fmt.Errorf("admission request object was of invalid type %T (expected *v1.MyKind)", req.Object)
}
// Set defaults on create
if req.Action == resource.AdmissionActionCreate {
if obj.Spec.Description == "" {
obj.Spec.Description = "No description provided"
}
}
return &app.MutatingResponse{UpdatedObject: obj}, nil
}
注册准入处理程序
在 pkg/app/app.go 中构建应用时注册验证器和变更器:
func New(cfg app.Config) (app.App, error) {
cfg.KubeConfig.APIPath = "/apis"
a, err := simple.NewApp(simple.AppConfig{
ManagedKinds: []simple.AppManagedKind{
{
Kind: v1.MyKindKind(),
Validator: &MyKindValidator{},
Mutator: &MyKindMutator{},
},
},
})
if err != nil {
return nil, fmt.Errorf("error creating app: %w", err)
}
if err = a.ValidateManifest(cfg.ManifestData); err != nil {
return nil, fmt.Errorf("app manifest validation failed: %w", err)
}
return a, nil
}
请注意,还必须在类型的 CUE 定义中启用变更和验证(mutation.operations 和 validation.operations 字段)——详情请参阅 cue-kind-definition 技能。
准入请求字段
app.AdmissionRequest 上可用的关键字段:
| 字段 | 类型 | 描述 |
|---|---|---|
Object | resource.Object | 传入的资源(解码后) |
OldObject | resource.Object | 先前状态(仅适用于 UPDATE 操作) |
Action | resource.AdmissionAction | AdmissionActionCreate、AdmissionActionUpdate、AdmissionActionDelete、AdmissionActionConnect |
UserInfo | resource.AdmissionUserInfo | 发起请求的用户 |
Kind | string | Object 类型 |
Group | string | Object API 组 |
Version | string | Object API 版本 |
验证模式
要实现的常见模式:
// Immutability check
if req.Action == resource.AdmissionActionUpdate && old.Spec.ImmutableField != obj.Spec.ImmutableField {
return fmt.Errorf("spec.immutableField cannot be changed after creation")
}
// Cross-field validation
if obj.Spec.StartTime.After(obj.Spec.EndTime) {
return fmt.Errorf("spec.startTime must be before spec.endTime")
}
// Referential validation (e.g. check referenced resource exists)
if _, err := v.client.Get(ctx, resource.Identifier{Name: obj.Spec.RefName, Namespace: obj.Namespace}); err != nil {
return fmt.Errorf("referenced resource %q not found", obj.Spec.RefName)
}
部署差异
| 模式 | 准入运行时 |
|---|---|
| 独立 Operator | 应用会启动 Webhook 服务;Kubernetes 会将准入请求路由到该服务 |
grafana/apps | 准入处理程序会自动注册为 Kubernetes 进程内插件——无需单独的服务 |
两种情况下的处理程序代码本身完全相同。
资源
评论
支持 Markdown