mcpField
A
admission-control

admission-control

github.com/grafana開発2954.2k 回インストール1 日前に更新
リポジトリ

admission-control について

Admission Controlは、grafana-app-sdkで管理するリソースの検証・変更ハンドラーを解説します。永続化前にリクエストを拒否またはオブジェクトを調整するアプリ開発者向けで、スタンドアロンとgrafana/appsの両方で同じハンドラー処理を使えます。

主な機能

  1. 01作成・更新リクエストの検証
  2. 02永続化前のリソース変更
  3. 03リクエストオブジェクトと操作情報へのアクセス
  4. 04管理対象の種類へのハンドラー登録
  5. 05スタンドアロンとgrafana/appsランタイムへの対応

活用シーン

  1. 01無効なリソースリクエストの拒否
  2. 02デフォルト値の設定やフィールドの正規化
  3. 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 で利用できる主なフィールド:

フィールド型説明
Objectresource.Object受信したリソース(デコード後)
OldObjectresource.Object以前の状態(UPDATE 操作の場合のみ)
Actionresource.AdmissionActionAdmissionActionCreate、AdmissionActionUpdate、AdmissionActionDelete、AdmissionActionConnect
UserInforesource.AdmissionUserInfoリクエストを行ったユーザー
KindstringObject の kind
GroupstringObject の API グループ
VersionstringObject の 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/appsAdmission ハンドラーは Kubernetes のインプロセスプラグインとして自動登録されるため、別のサーバーは不要です

ハンドラーコード自体は、どちらの場合も同じです。

リソース

コメント