Extending
Adding a New CRD
1. Define the types
Create api/v1alpha1/<name>_types.go:
package v1alpha1
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// +kubebuilder:object:root=true
// +kubebuilder:resource:scope=Cluster
// +kubebuilder:subresource:status
// +kubebuilder:printcolumn:name="Phase",type=string,JSONPath=`.status.phase`
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
type MyCRD struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec MyCRDSpec `json:"spec,omitempty"`
Status MyCRDStatus `json:"status,omitempty"`
}
type MyCRDSpec struct {
// +kubebuilder:validation:Required
SomeField string `json:"someField"`
}
type MyCRDStatus struct {
Phase string `json:"phase,omitempty"`
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +kubebuilder:object:root=true
type MyCRDList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []MyCRD `json:"items"`
}
func init() {
SchemeBuilder.Register(&MyCRD{}, &MyCRDList{})
}Rules:
- Must be cluster-scoped (
+kubebuilder:resource:scope=Cluster) - Status must include
[]metav1.Condition - Register in
init()viaSchemeBuilder
2. Generate code
make codegenThis produces:
api/v1alpha1/zz_generated.deepcopy.go(updated)config/crd/bases/drop.corewire.io_mycrds.yaml- RBAC roles in
config/rbac/
3. Write the controller
Create internal/controller/<name>_controller.go:
package controller
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/log"
dropv1alpha1 "github.com/Breee/drop/api/v1alpha1"
)
type MyCRDReconciler struct {
client.Client
Scheme *runtime.Scheme
}
// +kubebuilder:rbac:groups=drop.corewire.io,resources=mycrds,verbs=get;list;watch;update;patch
// +kubebuilder:rbac:groups=drop.corewire.io,resources=mycrds/status,verbs=get;update;patch
func (r *MyCRDReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
log := log.FromContext(ctx)
var obj dropv1alpha1.MyCRD
if err := r.Get(ctx, req.NamespacedName, &obj); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.Info("reconciling", "name", obj.Name)
// Business logic here
return ctrl.Result{}, nil
}
func (r *MyCRDReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&dropv1alpha1.MyCRD{}).
Complete(r)
}4. Register in cmd/main.go
if err = (&controller.MyCRDReconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "MyCRD")
os.Exit(1)
}5. Add tests
Unit test — internal/controller/<name>_controller_test.go:
- Use envtest suite
- Create the resource, trigger reconciliation, assert status
E2E test — test/e2e/<name>-basic/chainsaw-test.yaml:
- Apply resource, assert expected status/children
Sample — config/samples/drop_v1alpha1_<name>.yaml:
- Minimal valid resource for testing
6. Regenerate docs
make docs-genThis updates llms.txt, AGENTS.md, .cursorrules, knowledge.yaml, and the copilot instructions.
Last updated on