<p>I’ve  k8s operator which works as expected, I need to add a “watch” to other operator CRD (not mine), to make it simple lets call it <code>extCR</code> and our operator cr called <code>inCR</code>,</p>
<p>I tried the following but there is an issue how its right to trigger the reconcile.</p>
<pre><code>func (r *Insiconciler) SetupWithManager(mgr ctrl.Manager) error {
                return ctrl.NewControllerManagedBy(mgr).
                    For(&amp;Inv1alpha1.Iget{}}).
                    Watches(&amp;source.Kind{Type: &amp;ext.Se{}},  handler.EnqueueRequestsFromMapFunc(r.FWatch)).
                    Complete(r)
}
    
func (r *Insiconciler) FWatch(c client.Object) []reconcile.Request {
        val := c.(*ivi.Srv)
        req := reconcile.Request{NamespacedName: types.NamespacedName{Name: val.Name, Namespace: val.Namespace}}
        return []reconcile.Request{req}
}
</code></pre>
<p>The problem here that I trigger the reconcile with the <code>extCR</code> , I want inside the <code>FWatch</code> to update the <code>inCR</code> and start the reconcile with inCR and not with extCR, how can I do it ?</p>
<p>I mean, to avoid something like the following code as sometimes the reconcile is done for the <code>inCR</code> and sometimes for the <code>extCR</code> and than I can get some ugly if's</p>
<pre><code>func (r *Insiconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
        var inCR FOO
        var extCR BAR
    
        if err := r.Get(ctx, req.NamespacedName, &amp;inCR); err != nil {
            return ctrl.Result{}, err
        }
        
        if err := r.Get(ctx, req.NamespacedName, &amp;extCR); err != nil {
            return ctrl.Result{}, err
        }
</code></pre>
<p><strong>I want to know what is the right/clean way to handle such case</strong></p>
<p>case when you need to listen to externalCR (not part of your controller) and also internalCR (from your controller) .</p>
<p>One more thing - the CR are different GVK but the exteranlCR contain lot of fields which is not required, just some of them. but the <strong>required fields</strong> is having the <strong>same names</strong>   on both cr's</p>
<p><strong>update</strong></p>
<pre><code>type inCR struct {
    metav1.TypeMeta   `json:&quot;,inline&quot;`
    metav1.ObjectMeta `json:&quot;metadata,omitempty&quot;`

    Spec   inSpec  `json:&quot;spec,omitempty&quot;`  / / ————————here is the difference 
    Status InsightTargetStatus `json:&quot;status,omitempty&quot;`
}
</code></pre>
<p>//————— This is defined on other program which is not owned by us, therefore cannot “reuse”</p>
<pre><code>type Bar struct {
    metav1.TypeMeta   `json:&quot;,inline&quot;`
    metav1.ObjectMeta `json:&quot;metadata,omitempty&quot;`

    Spec   extSpec  `json:&quot;spec,omitempty&quot;`    // ———————here is the difference 
    Status ServiceStatus `json:&quot;status,omitempty&quot;`
}
</code></pre>
<p>And <code>inSpec</code> is having the following fields (subset of extSpec)</p>
<pre><code>type inSpec struct {
    name string
    age  int
}
</code></pre>
<p>and <code>extSpec</code> have those fields and many more which is not related</p>
<pre><code>type extSpec struct {
    name string   
    age  int
    foo string  // not relevant
    bar string  // not relevant
    bazz string // not relevant
}
</code></pre>
<p>at the end, <strong>Inside the reconcile</strong> I need to move the <code>relevant</code>  fields to some functions. <strong>exactl</strong>y same functions just take sometime the fields from extCR and sometimes for inCR, according to the event that happens (like updating the extCR or update the inCR by users )</p>
<p><strong>Update2</strong></p>
<pre><code>func sharedLogic(r reconciler, ctx context.Context, c client.Object) (ctrl.Result, error) {
    
    
    in := c.(*inCR)
    
    
    vPass , e := vps.Get(ctx, r.Client, in.Spec.foo, in.Spec.bar)
    
    
     return ctrl.Result{}, nil
    }

But for extCR I should do the following


func sharedLogic(r reconciler, ctx context.Context, c client.Object) (ctrl.Result, error) {


ext := c.(*extCR)


vPass , e := vps.Get(ctx, r.Client, ext.Spec.val.foo, ext.Spec.val.bar)


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

Jenney
<p>Few things to keep in mind:</p>
<ul>
<li>Each controller is responsible for <em>exactly</em> one resource.</li>
<li>Reconcile request contains the information necessary to reconcile a Kubernetes object. This includes the information to uniquely identify the object - its Name and Namespace. It does NOT contain information about any specific Event or the object contents itself.</li>
</ul>
<hr />
<p>You can create a second controller without the resource definition. In your main file, both controllers will be registered.</p>
<p>This could be useful if the CRDs are not related at all or if the external resource references the internal one, so you can make changes to the internal resource in the external reconciler.</p>
<pre class="lang-bash prettyprint-override"><code>kubebuilder create api --group other --version v2 --kind External \
 --resource=false --controller=true
</code></pre>
<p>This gives you a controller with a <code>SetupWithManager</code> method that looks like the below.</p>
<pre class="lang-golang prettyprint-override"><code>func (r *ExternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        // Uncomment the following line adding a pointer to an instance of the controlled resource as an argument
        // For().
        Complete(r)
}
</code></pre>
<p>Note how the For method is commented out because you need to import the resource to watch from somewhere else and reference it.</p>
<pre class="lang-golang prettyprint-override"><code>import (
    ...
    otherv2 &quot;other.io/external/api/v2&quot;
)
...
func (r *ExternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&amp;otherv2.External{}).
        Complete(r)
}
</code></pre>
<p>If you cannot import the external resource you could fall back to mocking it yourself but this is probably not a very clean way. You should really try to import it from the other controller project.</p>
<pre class="lang-bash prettyprint-override"><code>kubebuilder edit --multigroup=true
kubebuilder create api --group=other --version v2 --kind External \
  --resource --controller
</code></pre>
<hr />
<p>Another way is when the resources are related to each other such that the internal resource has a reference in its spec to the external resource and knows how to get the external resource in its spec, when it reconciles. An example of this can be found here <a href="https://book.kubebuilder.io/reference/watching-resources/externally-managed.html" rel="nofollow noreferrer">https://book.kubebuilder.io/reference/watching-resources/externally-managed.html</a></p>
<pre class="lang-golang prettyprint-override"><code>type InternalSpec struct {
    // Name of an external resource
    ExternalResource string `json:&quot;externalResource,omitempty&quot;`
}
</code></pre>
<p>This means that in each reconciliation loop, the controller will look up the external resource and use it to manage the internal resource.</p>
<pre class="lang-golang prettyprint-override"><code>func (r *InternalReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
    _ = log.FromContext(ctx)

    internal := examplev1.Internal{}
    if err := r.Get(context.TODO(), types.NamespacedName{
        Name:      req.Name,
        Namespace: req.Namespace,
    }, &amp;internal); err != nil {
        return ctrl.Result{}, err
    }

    external := otherv2.External{}
    if err := r.Get(context.TODO(), types.NamespacedName{
        // note how the name is taken from the internal spec
        Name:      internal.Spec.ExternalResource,
        Namespace: req.Namespace,
    }, &amp;internal); err != nil {
        return ctrl.Result{}, err
    }

    // do something with internal and external here

    return ctrl.Result{}, nil
}
</code></pre>
<p>The problem with this is, that when the internal resource does not change, no reconciliation event will be triggered, even when the external resource has changed. To work around that, we can trigger the reconciliation by watching the external resource. Note the <code>Watches</code> method:</p>
<pre class="lang-golang prettyprint-override"><code>func (r *InternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
    return ctrl.NewControllerManagedBy(mgr).
        For(&amp;examplev1.Main{}).
        Watches(
            &amp;source.Kind{Type: &amp;otherv2.ExternalResource{}},
            handler.EnqueueRequestsFromMapFunc(r.triggerReconcileBecauseExternalHasChanged),
            builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}),
        ).
        Complete(r)
}
</code></pre>
<p>In order to know for which internal object we should trigger an event, we use a mapping function to look up all the internal that have a reference to the external resource.</p>
<pre class="lang-golang prettyprint-override"><code>func (r *InternalReconciler) triggerReconcileBecauseExternalHasChanged(o client.Object) []reconcile.Request {
    usedByInternals := &amp;examplev1.InternalList{}
    listOps := &amp;client.ListOptions{
        FieldSelector: fields.OneTermEqualSelector(&quot;.spec.ExternalResource&quot;, o.GetName()),
        Namespace:     o.GetNamespace(),
    }
    err := r.List(context.TODO(), usedByInternals, listOps)
    if err != nil {
        return []reconcile.Request{}
    }
    requests := make([]reconcile.Request, len(usedByInternals.Items))
    for i, item := range usedByInternals.Items {
        requests[i] = reconcile.Request{
            NamespacedName: types.NamespacedName{
                Name:      item.GetName(),
                Namespace: item.GetNamespace(),
            },
        }
    }
    return requests
}
</code></pre>
<hr />
<p>Since you updated your question, I suggest doing something like below.</p>
<p>I am creating a new project and 2 controllers. Note on the second controller command no resource is created along with the controller. this is because the controller
will watch an external resource.</p>
<pre class="lang-bash prettyprint-override"><code>mkdir demo &amp;&amp; cd demo
go mod init example.io/demo
kubebuilder init --domain example.io --repo example.io/demo --plugins=go/v4-alpha
kubebuilder create api --group=demo --version v1 --kind Internal --controller --resource
kubebuilder create api --group=other --version v2 --kind External --controller --resource=false
</code></pre>
<pre class="lang-bash prettyprint-override"><code>$ tree controllers
controllers
├── external_controller.go
├── internal_controller.go
└── suite_test.go
</code></pre>
<p>Now we need some shared logic, for example by adding this to the controllers package. We will call this from both reconcilers.</p>
<pre class="lang-golang prettyprint-override"><code>// the interface may need tweaking
// depending on what you want to do with
// the reconiler
type reconciler interface {
 client.Reader
 client.Writer
 client.StatusClient
}

func sharedLogic(r reconciler, kobj *demov1.Internal) (ctrl.Result, error) {
 // do your shared logic here operating on the internal object struct
 // this works out because the external controller will call this passing the
 // internal object
 return ctrl.Result{}, nil
}
</code></pre>
<p>Here is an example for the internal reconciler.</p>
<pre class="lang-golang prettyprint-override"><code>func (r *InternalReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 _ = log.FromContext(ctx)
 obj := demov1.Internal{}
 if err := r.Get(ctx, req.NamespacedName, &amp;obj); err != nil {
  return ctrl.Result{}, err
 }
 return sharedLogic(r, &amp;obj)
}
</code></pre>
<p>And in the external reconciler we do the same.</p>
<pre class="lang-golang prettyprint-override"><code>func (r *ExternalReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
 _ = log.FromContext(ctx)
 // note, we can use the internal object here as long as the external object
 // does contain the same fields we want. That means when unmarshalling the extra
 // fields are dropped. If this cannot be done, you could first unmarshal into the external
 // resource and then assign the fields you need to the internal one, before passing it down
 obj := demov1.Internal{}
 if err := r.Get(ctx, req.NamespacedName, &amp;obj); err != nil {
  return ctrl.Result{}, err
 }
 return sharedLogic(r, &amp;obj)
}

func (r *ExternalReconciler) SetupWithManager(mgr ctrl.Manager) error {
 return ctrl.NewControllerManagedBy(mgr).
 // note the external resource is imported from another project
 // you may be able to watch this without import by creating a minimal
 // type with the right GKV
  For(otherv2.External{}).
  Complete(r)
}
</code></pre>
