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
آخر نشاط في المصدر
٢٣ فبراير ٢٠٢٦ في ٠٩:٥١
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٧٬٣٩٤
التفرعات
١٬٦٢١

خيارات التثبيت

يُحدَّد 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