Operator and Nil objects

22 views
Skip to first unread message

Luigi Tagliamonte

unread,
Jul 10, 2024, 5:55:21 PM7/10/24
to Operator Framework
Dear community,
I'm a long k8s user but I'm first timer user of the operator framework...
I defined my CRD to look like:
```
// MyClusterSpec defines the desired state of MyCluster
// +k8s:openapi-gen=true
type MyClusterSpec struct {
// ClusterVersion defines default images tag for all components.
// it can be overwritten with component specific image.tag value.
// +optional
ClusterVersion string `json:"clusterVersion,omitempty"`

// Cluster shard size, default 10, will create 10 indexers and 10 fetchers.
// +optional
// +kubebuilder:default:=10
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Value is immutable"
ShardCount int32 `json:"shardCount,omitempty"`

// Indexers configuration
// +optional
Indexer *Indexer `json:"indexer,omitempty"`

// Rcm configuration
// +optional
Rcm *Rcm `json:"rcm,omitempty"`

// Fetchers configuration
// +optional
Fetcher *Fetcher `json:"fetcher,omitempty"`
}
```
When i run the code and I create a basic MyCluster, I see Fetcher, RCM and Indexer objects all nil and this causes me to have to implement a lot o nil checks.
Is there a smarter way to approach this?

robinhood-me

unread,
Jul 11, 2024, 8:36:21 AM7/11/24
to Operator Framework

To handle the nil checks in a more efficient way, you can initialize the optional fields with default values. This way, you avoid having to check for nil in your operator code, and you can work with these fields directly. Here's a refined approach:

Default Initialization
  1. Default Initialization in the Operator: Ensure that when the MyCluster object is created, it initializes the optional fields if they are nil. This can be done in your reconciliation logic.

  2. Webhooks for Defaulting: Implement a Kubernetes admission webhook to set default values for the optional fields. This is a more Kubernetes-native way to handle defaults and can be managed by the operator framework.

Example of Default Initialization in the Operator

Modify your reconciliation logic to check and initialize the optional fields if they are nil:

func (r *MyClusterReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
log := r.Log.WithValues("mycluster", req.NamespacedName)

// Fetch the MyCluster instance
myCluster := &mydomainv1.MyCluster{}
err := r.Get(ctx, req.NamespacedName, myCluster)
if err != nil {
if apierrors.IsNotFound(err) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
}

// Initialize optional fields if they are nil
if myCluster.Spec.Indexer == nil {
myCluster.Spec.Indexer = &Indexer{}
}
if myCluster.Spec.Rcm == nil {
myCluster.Spec.Rcm = &Rcm{}
}
if myCluster.Spec.Fetcher == nil {
myCluster.Spec.Fetcher = &Fetcher{}
}

// Your reconciliation logic here...

return ctrl.Result{}, nil
}

Using Mutating Admission Webhook for Defaulting

You can create a mutating admission webhook to set default values for your CRD. Here's an example of how you can achieve that:

  1. Define the webhook:
func (r *MyCluster) Default() {
if r.Spec.Indexer == nil {
r.Spec.Indexer = &Indexer{}
}
if r.Spec.Rcm == nil {
r.Spec.Rcm = &Rcm{}
}
if r.Spec.Fetcher == nil {
r.Spec.Fetcher = &Fetcher{}
}
}

  1. Register the webhook:

In your main.go or wherever you set up the manager, you need to register the webhook:

func main() {
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}

if err = (&mydomainv1.MyCluster{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "MyCluster")
os.Exit(1)
}

// Other setup code...
}

func main() {
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
})
if err != nil {
setupLog.Error(err, "unable to start manager")
os.Exit(1)
}

if err = (&mydomainv1.MyCluster{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "MyCluster")
os.Exit(1)
}

// Other setup code...
}

  1. Webhook setup for your CRD:

Make sure your CRD YAML includes the webhook configuration:

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: mycluster-mutating-webhook
webhooks:
  - name: mmycluster.mydomain.com
    clientConfig:
      service:
        name: mycluster-webhook-service
        namespace: my-namespace
        path: /mutate-mydomain-v1-mycluster
        port: 443
      caBundle: ...
    rules:
      - operations: ["CREATE", "UPDATE"]
        apiGroups: ["mydomain.com"]
        apiVersions: ["v1"]
        resources: ["myclusters"]
    admissionReviewVersions: ["v1"]
    sideEffects: None

you can ensure that the optional fields are always initialized, reducing the need for nil checks throughout your code. This makes your operator logic cleaner and more reliable.

Reply all
Reply to author
Forward
0 new messages