<p>I'm using the <a href="https://github.com/operator-framework/operator-sdk" rel="noreferrer">Operator SDK</a> to build a custom Kubernetes operator. I have created a custom resource definition and a controller using the respective Operator SDK commands:</p>

<pre><code>operator-sdk add api --api-version example.com/v1alpha1 --kind=Example
operator-sdk add controller --api-version example.com/v1alpha1 --kind=Example
</code></pre>

<p>Within the main reconciliation loop (for the example above, the auto-generated <code>ReconcileExample.Reconcile</code> method) I have some custom business logic that requires me to query the Kubernetes API for other objects of the same kind that have a certain field value. It's occurred to me that I might be able to use the default API client (that is provided by the controller) with a custom field selector:</p>

<pre><code>func (r *ReconcileExample) Reconcile(request reconcile.Request) (reconcile.Result, error) {
    ctx := context.TODO()
    listOptions := client.ListOptions{
        FieldSelector: fields.SelectorFromSet(fields.Set{"spec.someField": "someValue"}),
        Namespace:     request.Namespace,
    }
    otherExamples := v1alpha1.ExampleList{}

    if err := r.client.List(ctx, &amp;listOptions, &amp;otherExamples); err != nil {
        return reconcile.Result{}, err
    }

    // do stuff...

    return reconcile.Result{}, nil
}
</code></pre>

<p>When I run the operator and create a new <code>Example</code> resource, the operator fails with the following error message:</p>

<pre><code>{"level":"info","ts":1563388786.825384,"logger":"controller_example","msg":"Reconciling Example","Request.Namespace":"default","Request.Name":"example-test"}
{"level":"error","ts":1563388786.8255732,"logger":"kubebuilder.controller","msg":"Reconciler error","controller":"example-controller","request":"default/example-test","error":"Index with name field:spec.someField does not exist","stacktrace":"..."}
</code></pre>

<p>The most important part being</p>

<blockquote>
  <p>Index with name field:spec.someField does not exist</p>
</blockquote>

<p>I've already searched <a href="https://github.com/operator-framework/operator-sdk/blob/master/doc/user/client.md" rel="noreferrer">the Operator SDK's documentation</a> on the default API client and learned a bit about the inner workings of the client, but no detailed explanation on this error or how to fix it.</p>

<p><strong>What does this error message mean, and how can I create this missing index to efficiently list objects by this field value?</strong></p>

<p>The default API client that is provided by the controller is a <em>split client</em> -- it serves <code>Get</code> and <code>List</code> requests from a locally-held cache and forwards other methods like <code>Create</code> and <code>Update</code> directly to the Kubernetes API server. This is also explained in <a href="https://github.com/operator-framework/operator-sdk/blob/master/doc/user/client.md#default-client" rel="noreferrer">the respective documentation</a>:</p>

<blockquote>
  <p>The SDK will generate code to create a Manager, which holds a Cache and a Client to be used in CRUD operations and communicate with the API server. By default a Controller's Reconciler will be populated with the Manager's Client which is a split-client. [...] A split client reads (Get and List) from the Cache and writes (Create, Update, Delete) to the API server. Reading from the Cache significantly reduces request load on the API server; as long as the Cache is updated by the API server, read operations are eventually consistent.</p>
</blockquote>

<p>To query values from the cache using a custom field selector, the cache needs to have a <em>search index</em> for this field. This indexer can be defined right after the cache has been set up.</p>

<p>To register a custom indexer, add the following code into the bootstrapping logic of the operator (in the auto-generated code, this is done directly in <code>main</code>). This needs to be done <strong>after</strong> the controller manager has been instantiated (<code>manager.New</code>) and <strong>also after</strong> the custom API types have been added to the <code>runtime.Scheme</code>:</p>

<pre><code>package main

import (
    k8sruntime "k8s.io/apimachinery/pkg/runtime"
    "example.com/example-operator/pkg/apis/example/v1alpha1"
    // ...
)

function main() {
    // ...

    cache := mgr.GetCache()

    indexFunc := func(obj k8sruntime.Object) []string {
        return []string{obj.(*v1alpha1.Example).Spec.SomeField}
    }

    if err := cache.IndexField(&amp;v1alpha1.Example{}, "spec.someField", indexFunc); err != nil {
        panic(err)
    }

    // ...
}
</code></pre>

<p>When a respective indexer function is defined, field selectors on <code>spec.someField</code> will work from the local cache as expected.</p>
