admission-control
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseAdmission Control
Admission Control
Admission control intercepts resource create/update requests before they are persisted. In grafana-app-sdk there are two types:
- Validation — accept or reject a request; cannot modify the resource
- Mutation — modify the resource before it is persisted (e.g. set defaults, normalize fields)
The app business logic for admission is identical whether the app runs as a standalone operator or inside . The only difference is the runtime: standalone apps stand up their own webhook server; apps have admission auto-registered as a Kubernetes plugin.
grafana/appsgrafana/appsAdmission Control会在资源持久化之前拦截资源的创建/更新请求。在grafana-app-sdk中有两种类型:
- Validation(验证) — 接受或拒绝请求;无法修改资源
- Mutation(变异) — 在资源持久化之前修改资源(例如设置默认值、标准化字段)
无论应用作为独立operator运行还是在内部运行,准入控制的业务逻辑都是相同的。唯一的区别在于运行时:独立应用会启动自己的Webhook服务器;中的应用会将准入控制自动注册为Kubernetes插件。
grafana/appsgrafana/appsGetting Stubs
获取代码模板
For standalone apps, if does not yet exist, a stub App can be generated with:
pkg/app/app.gobash
grafana-app-sdk project component add operatorThis creates scaffolded which admission handlers can be added to for each kind in .
simple.AppManagedKinds对于独立应用,如果尚未存在,可以通过以下命令生成一个Stub App:
pkg/app/app.gobash
grafana-app-sdk project component add operator这会创建一个包含脚手架的,可以为中的每种类型添加准入处理器。
simple.AppManagedKindsValidator Interface
Validator接口
go
// Implement this interface for each kind you want to validate
type Validator interface {
Validate(ctx context.Context, request *app.AdmissionRequest) error
}- Return to admit the request
nil - Return an error to reject it (the error message is returned to the API caller)
- provides access to the incoming object and operation type
app.AdmissionRequest - You can use (from
k8s.NewAdmissionError(err error, statusCode int, reason string)) to better control the returned error information"github.com/grafana/grafana-app-sdk/k8s"
go
// 为每个需要验证的类型实现此接口
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"
Validator Example
Validator示例
go
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
}go
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)
}
// 验证spec字段
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)
}
// 区分创建与更新操作
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
}Mutating Admission (Mutator)
变异准入控制(Mutator)
go
// Implement this interface to mutate resources before persistence
type Mutator interface {
Mutate(ctx context.Context, request *app.AdmissionRequest) (*app.MutatingResponse, error)
}- Return a containing the (optionally modified) object
MutatingResponse - Return an error to reject the request entirely
- Best practice is to reject requests from validators, not mutators
go
// 实现此接口以在持久化前修改资源
type Mutator interface {
Mutate(ctx context.Context, request *app.AdmissionRequest) (*app.MutatingResponse, error)
}- 返回包含(可选修改后的)对象的
MutatingResponse - 返回错误表示完全拒绝请求
- 最佳实践是通过验证器而非变异器拒绝请求
Mutating Handler Example
变异处理器示例
go
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
}go
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)
}
// 在创建时设置默认值
if req.Action == resource.AdmissionActionCreate {
if obj.Spec.Description == "" {
obj.Spec.Description = "No description provided"
}
}
return &app.MutatingResponse{UpdatedObject: obj}, nil
}Registering Admission Handlers
注册准入处理器
Register validators and mutators when building the app in :
pkg/app/app.gogo
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
}Note that mutation and validation must also be enabled in the kind's CUE definition ( and fields) — see the skill for details.
mutation.operationsvalidation.operationscue-kind-definition在中构建应用时注册验证器和变异器:
pkg/app/app.gogo
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.operationsvalidation.operationscue-kind-definitionAdmission Request Fields
Admission Request字段
Key fields available on :
app.AdmissionRequest| Field | Type | Description |
|---|---|---|
| | The incoming resource (after decoding) |
| | Previous state (only on UPDATE operations) |
| | |
| | The user making the request |
| | The |
| | The |
| | The |
app.AdmissionRequest| 字段 | 类型 | 描述 |
|---|---|---|
| | 传入的资源(解码后) |
| | 之前的状态(仅在UPDATE操作中可用) |
| | |
| | 发起请求的用户信息 |
| | |
| | |
| | |
Validation Patterns
验证模式
Common patterns to implement:
go
// 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)
}常见的实现模式:
go
// 不可变性检查
if req.Action == resource.AdmissionActionUpdate && old.Spec.ImmutableField != obj.Spec.ImmutableField {
return fmt.Errorf("spec.immutableField cannot be changed after creation")
}
// 跨字段验证
if obj.Spec.StartTime.After(obj.Spec.EndTime) {
return fmt.Errorf("spec.startTime must be before spec.endTime")
}
// 引用验证(例如检查引用的资源是否存在)
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)
}Deployment Difference
部署差异
| Mode | Admission runtime |
|---|---|
| Standalone operator | App starts a webhook server; Kubernetes routes admission requests to it |
| Admission handlers are auto-registered as a Kubernetes in-process plugin — no separate server required |
The handler code itself is identical in both cases.
| 模式 | 准入控制运行时 |
|---|---|
| 独立operator | 应用启动Webhook服务器;Kubernetes将准入请求路由到该服务器 |
| 准入处理器自动注册为Kubernetes进程内插件 — 无需单独服务器 |
两种模式下的处理器代码本身完全相同。