Skip to main content

implement-calico-api-resource

Implements a new Calico API resource by plumbing it through all layers of the codebase. Use after API design is complete (see design-kubernetes-api skill).

소스 정보

저장소
projectcalico/calico
최근 소스 활동
2026년 2월 23일 09:51
감지된 SKILL.md 언어
영어
스타
7,394
포크
1,621

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
implement-calico-api-resource
description
Implements a new Calico API resource by plumbing it through all layers of the codebase. Use after API design is complete (see design-kubernetes-api skill).
## Prerequisites Before using this skill, ensure you have: - A designed API resource (Go structs with kubebuilder annotations, json tags, etc.) — use the `design-kubernetes-api` skill first. - Know whether the resource is **namespaced** or **cluster-scoped**. - Know the **Kind**, **plural name**, and any **short names** for kubectl. ## Overview of Layers Adding a new Calico API resource touches these layers (in dependency order): 1. **API type definition** (`api/pkg/apis/projectcalico/v3/`) 2. **Code generation** (deepcopy, clients, informers, listers, OpenAPI) 3. **CRD operator types** (`libcalico-go/lib/apis/crd.projectcalico.org/v1/`) 4. **CRD v1 scheme registration** (`libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go`) 5. **Backend model registration** (`libcalico-go/lib/backend/model/resource.go`) 6. **K8s backend resource client** (`libcalico-go/lib/backend/k8s/resources/`) 7. **K8s backend client registration** (`libcalico-go/lib/backend/k8s/client.go`) 8. **Namespace helper** (if namespaced) (`libcalico-go/lib/namespace/resource.go`) 9. **Validator** (`libcalico-go/lib/validator/v3/validator.go`) 10. **clientv3 typed client** (`libcalico-go/lib/clientv3/`) 11. **Apiserver registry** (storage, strategy, REST) (`apiserver/pkg/registry/projectcalico/`) 12. **Apiserver Calico storage adapter** (`apiserver/pkg/storage/calico/`) 13. **Apiserver storage interface switch** (`apiserver/pkg/storage/calico/storage_interface.go`) 14. **Apiserver converter** (`apiserver/pkg/storage/calico/converter.go`) 15. **Apiserver REST storage provider** (`apiserver/pkg/registry/projectcalico/rest/storage_calico.go`) 16. **Felix syncer** (if Felix needs this resource) (`libcalico-go/lib/backend/syncersv1/felixsyncer/`) 17. **RBAC** (Helm chart RBAC for node/operator) 18. **Manifests and CRD YAML** (generated) ## Workflow Work through the following steps in order. Each step references specific files and patterns to follow. ### Step 1: API Type Definition Create the Go type file in `api/pkg/apis/projectcalico/v3/`. **File:** `api/pkg/apis/projectcalico/v3/<resourcename>.go` Follow this pattern (using BGPFilter as a clean example): ```go // Copyright (c) <YEAR> Tigera, Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 ... package v3 import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" const ( KindMyResource = "MyResource" KindMyResourceList = "MyResourceList" ) // +genclient:nonNamespaced (cluster-scoped only) // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // MyResourceList contains a list of MyResource resources. type MyResourceList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Items []MyResource `json:"items" protobuf:"bytes,2,rep,name=items"` } // For cluster-scoped: // +genclient // +genclient:nonNamespaced // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:resource:scope=Cluster,shortName={myres} // For namespaced: // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:resource:scope=Namespaced,shortName={myres} // MyResource represents <description suitable for CRD schema docs>. type MyResource struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata" protobuf:"bytes,1,opt,name=metadata"` Spec MyResourceSpec `json:"spec" protobuf:"bytes,2,opt,name=spec"` } // MyResourceSpec contains the specification for a MyResource resource. type MyResourceSpec struct { // ... fields with kubebuilder validation annotations } // NewMyResource creates a new (zeroed) MyResource struct with TypeMetadata initialised. func NewMyResource() *MyResource { return &MyResource{ TypeMeta: metav1.TypeMeta{ Kind: KindMyResource, APIVersion: GroupVersionCurrent, }, } } ``` **Key annotations:** - `+genclient` — generates typed client code - `+genclient:nonNamespaced` — for cluster-scoped resources (put on BOTH the list type and the main type) - `+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object` — generates DeepCopyObject() - `+kubebuilder:resource:scope=Cluster|Namespaced` — CRD scope - `+kubebuilder:resource:shortName={...}` — kubectl short names **Gotcha:** The `List` type needs `+genclient:nonNamespaced` too (for cluster-scoped resources), and `+k8s:deepcopy-gen:interfaces=...` on both types. Do NOT put `+kubebuilder:resource` on the List type — only on the main resource type. ### Step 2: Register in API Scheme **File:** `api/pkg/apis/projectcalico/v3/register.go` Add both types to the `AllKnownTypes` slice: ```go AllKnownTypes = []runtime.Object{ // ... existing types ... &MyResource{}, &MyResourceList{}, } ``` ### Step 3: Run API Code Generation ```bash cd api && make gen-files ``` This generates the DeepCopy methods, typed Kubernetes client, informers, listers, and OpenAPI schema needed for compilation of downstream layers: - `api/pkg/apis/projectcalico/v3/zz_generated.deepcopy.go` — DeepCopy methods - `api/pkg/client/clientset_generated/` — typed Kubernetes client - `api/pkg/client/informers_generated/` — informer factories - `api/pkg/client/listers_generated/` — listers - `api/pkg/openapi/generated.openapi.go` — OpenAPI schema Run this early so the remaining steps can compile against the generated types. A full `make generate` at the project root is still needed later (Step 19) to pick up CRDs, manifests, and other downstream generated files. ### Step 4: CRD Operator Types (crd.projectcalico.org/v1) Calico has a dual-CRD system. Older clusters use `crd.projectcalico.org/v1` CRDs, newer ones use `projectcalico.org/v3`. New resources need a type definition in the v1 scheme for backward compatibility. **File:** `libcalico-go/lib/apis/crd.projectcalico.org/v1/<myresource>_types.go` ```go package v1 import ( v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // +genclient // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // +kubebuilder:resource:scope=Cluster // or Namespaced type MyResource struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` Spec v3.MyResourceSpec `json:"spec,omitempty"` } ``` Note: the v1 type re-uses the v3 Spec struct — only the top-level wrapper differs. ### Step 5: Register in CRD v1 Scheme **File:** `libcalico-go/lib/apis/crd.projectcalico.org/v1/scheme/scheme.go` Add the type to the `BuilderCRDv1()` function's `AddKnownTypes` call: ```go &apiv3.MyResource{}, &apiv3.MyResourceList{}, ``` ### Step 6: Backend Model Registration **File:** `libcalico-go/lib/backend/model/resource.go` Add to the `init()` function: ```go registerResourceInfo[apiv3.MyResource](apiv3.KindMyResource, "myresources") ``` The plural name here must match the CRD plural. This enables the generic `ResourceKey`-based storage path. ### Step 7: K8s Backend Resource Client **File:** `libcalico-go/lib/backend/k8s/resources/<myresource>.go` For simple resources that use the v3 CRDs directly (typical for new resources): ```go package resources import ( "reflect" apiv3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "k8s.io/client-go/rest" ) const ( MyResourceResourceName = "MyResources" ) func NewMyResourceClient(r rest.Interface, group BackingAPIGroup) K8sResourceClient { return &customResourceClient{ restClient: r, resource: MyResourceResourceName, k8sResourceType: reflect.TypeOf(apiv3.MyResource{}), k8sListType: reflect.TypeOf(apiv3.MyResourceList{}), kind: apiv3.KindMyResource, apiGroup: group, } } ``` **Gotcha:** The `resource` field is the CRD resource name (plural, PascalCase used by the REST client). ### Step 8: Register in K8s Backend Client **File:** `libcalico-go/lib/backend/k8s/client.go` Add to the resource client registration block: ```go c.registerResourceClient( reflect.TypeOf(model.ResourceKey{}), reflect.TypeOf(model.ResourceListOptions{}), apiv3.KindMyResource, resources.NewMyResourceClient(restClient, group), ) ``` If there is any CRD cleanup or garbage-collection mechanism for v3 kinds in this client, ensure your new kind is included there as appropriate. ### Step 9: Namespace Helper (if namespaced) **File:** `libcalico-go/lib/namespace/resource.go` If your resource is namespaced, add its Kind to the `IsNamespaced` switch: ```go func IsNamespaced(kind string) bool { switch kind { case // ... existing cases ..., apiv3.KindMyResource: return true // ... } } ``` ### Step 10: Validator **File:** `libcalico-go/lib/validator/v3/validator.go` **Prefer kubebuilder annotations** for validation wherever possible (e.g., `+kubebuilder:validation:Enum`, `+kubebuilder:validation:Pattern`, `+kubebuilder:validation:Required`). Kubebuilder annotations generate CRD schema validation that is enforced by the Kubernetes API server. The Go struct validator in this file is only executed by the `crd.projectcalico.org/v1` code path — it is **not** executed for `projectcalico.org/v3` CRDs, so any validation that only lives here will be silently skipped on newer clusters. If your resource needs cross-field or complex validation that cannot be expressed with kubebuilder annotations, register a struct validator: ```go // In the init/registration function: registerStructValidator(validate, validateMyResourceSpec, api.MyResourceSpec{}) // Validation function: func validateMyResourceSpec(structLevel validator.StructLevel) { spec := structLevel.Current().Interface().(api.MyResourceSpec) // ... validation logic ... } ``` Simple resources may not need custom validation if kubebuilder annotations are sufficient. ### Step 11: clientv3 Typed Client **File:** `libcalico-go/lib/clientv3/<myresource>.go` Create the typed client interface and implementation: ```go package clientv3 import ( "context" v3 "github.com/projectcalico/api/pkg/apis/projectcalico/v3" "github.com/projectcalico/calico/libcalico-go/lib/options" validator "github.com/projectcalico/calico/libcalico-go/lib/validator/v3" "github.com/projectcalico/calico/libcalico-go/lib/watch" ) // MyResourceInterface has methods to work with MyResource resources. type MyResourceInterface interface { Create(ctx context.Context, res *v3.MyResource, opts options.SetOptions) (*v3.MyResource, error) Update(ctx context.Context, res *v3.MyResource, opts options.SetOptions) (*v3.MyResource, error) Delete(ctx context.Context, name string, opts options.DeleteOptions) (*v3.MyResource, error) Get(ctx context.Context, name string, opts options.GetOptions) (*v3.MyResource, error) List(ctx context.Context, opts options.ListOptions) (*v3.MyResourceList, error) Watch(ctx context.Context, opts options.ListOptions) (watch.Interface, error) } // myResources implements MyResourceInterface type myResources struct { client client } func (r myResources) Create(ctx context.Context, res *v3.MyResource, opts options.SetOptions) (*v3.MyResource, error) { if err := validator.Validate(res); err != nil { return nil, err } out, err := r.client.resources.Create(ctx, opts, v3.KindMyResource, res) if out != nil { return out.(*v3.MyResource), err } return nil, err } // ... Update, Delete, Get, List, Watch follow the same pattern. // For namespaced: Delete/Get take (namespace, name) instead of just name. // For namespaced: use namespace parameter instead of noNamespace constant. ``` **File:** `libcalico-go/lib/clientv3/interface.go` Add the client interface: ```go type MyResourceClient interface { MyResources() MyResourceInterface } ``` Add `MyResourceClient` to the main `Interface` interface. **File:** `libcalico-go/lib/clientv3/client.go` Add the accessor method: ```go func (c client) MyResources() MyResourceInterface { return myResources{client: c} } ``` ### Step 12: Apiserver Registry Create a new directory: `apiserver/pkg/registry/projectcalico/<myresource>/` **File:** `apiserver/pkg/registry/projectcalico/<myresource>/storage.go` Follow the pattern from `apiserver/pkg/registry/projectcalico/ipamconfig/storage.go`: - `EmptyObject()` returns `&calico.MyResource{}` - `NewList()` returns `&calico.MyResourceList{}` - `NewREST()` creates the registry.Store with: - `KeyRootFunc` / `KeyFunc` using `opts.KeyRootFunc(namespaced)` / `opts.KeyFunc(namespaced)`
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기