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
詳細
Admission Control
Admission control は、リソースの作成/更新リクエストが永続化される前にインターセプトします。grafana-app-sdk には、次の 2 種類があります。
- 検証 — リクエストを許可または拒否します。リソースは変更できません
- 変更 — 永続化する前にリソースを変更します(例:デフォルト値の設定、フィールドの正規化)
アプリがスタンドアロンオペレーターとして実行される場合も、grafana/apps 内で実行される場合も、Admission のアプリビジネスロジックは同じです。異なるのはランタイムのみです。スタンドアロンアプリは独自の Webhook サーバーを起動しますが、grafana/apps アプリでは Kubernetes プラグインとして Admission が自動登録されます。
スタブの取得
スタンドアロンアプリの場合、pkg/app/app.go がまだ存在しなければ、次のコマンドでスタブ App を生成できます。
grafana-app-sdk project component add operator
これにより、ManagedKinds の各 kind に対して Admission ハンドラーを追加できる、スキャフォールド済みの simple.App が作成されます。
Validator インターフェース
// 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から、受信したオブジェクトと操作の種類にアクセスできます- 返されるエラー情報をより詳細に制御するには、
"github.com/grafana/grafana-app-sdk/k8s"のk8s.NewAdmissionError(err error, statusCode int, reason string)を使用できます
Validator の例
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
}
Admission の変更(Mutator)
// Implement this interface to mutate resources before persistence
type Mutator interface {
Mutate(ctx context.Context, request *app.AdmissionRequest) (*app.MutatingResponse, error)
}
- (必要に応じて変更された)オブジェクトを含む
MutatingResponseを返します - リクエストを完全に拒否するにはエラーを返します
- ベストプラクティスとして、Mutator ではなく Validator でリクエストを拒否してください
変更ハンドラーの例
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
}
Admission ハンドラーの登録
pkg/app/app.go でアプリをビルドするときに、Validator と Mutator を登録します。
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
}
変更と検証は、kind の CUE 定義(mutation.operations フィールドと validation.operations フィールド)でも有効にする必要があります。詳細は cue-kind-definition スキルを参照してください。
Admission リクエストのフィールド
app.AdmissionRequest で利用できる主なフィールド:
| フィールド | 型 | 説明 |
|---|---|---|
Object | resource.Object | 受信したリソース(デコード後) |
OldObject | resource.Object | 以前の状態(UPDATE 操作の場合のみ) |
Action | resource.AdmissionAction | AdmissionActionCreate、AdmissionActionUpdate、AdmissionActionDelete、AdmissionActionConnect |
UserInfo | resource.AdmissionUserInfo | リクエストを行ったユーザー |
Kind | string | Object の kind |
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)
}
デプロイ方法の違い
| モード | Admission ランタイム |
|---|---|
| スタンドアロンオペレーター | App が Webhook サーバーを起動し、Kubernetes が Admission リクエストをそこにルーティングします |
grafana/apps | Admission ハンドラーは Kubernetes のインプロセスプラグインとして自動登録されるため、別のサーバーは不要です |
ハンドラーコード自体は、どちらの場合も同じです。