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).

Informações da origem

Repositório
projectcalico/calico
Última atividade na origem
23 de fevereiro de 2026 às 09:51
Idioma detectado do SKILL.md
inglês
Estrelas
7.394
Forks
1.621

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
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)`
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub