Repository navigation
[BUG] GitTarget and ClusterWatchRule don't react to referenced dependencies appearing; up to 2-min lag on first install #145
Description
Describe the bug
GitTarget and ClusterWatchRule do not react to their referenced dependencies appearing on the cluster. When a dependent CR is applied before its dependency exists, the dependent reaches a "not found" terminal status and only recovers on the next periodic requeue — up to ~2 minutes later. From an operator's point of view this looks like the controller is stuck.
The root cause is that the SetupWithManager wiring for both controllers omits a Watches(...) on the kind they reference, so an arriving dependency raises no event for the dependent.
To Reproduce
On a cluster with gitops-reverser 0.24.0 installed (no GitProvider, GitTarget, or ClusterWatchRule yet):
- Apply a
ClusterWatchRuleand aGitTargetfirst, before theGitProviderthey reference exists. (Easy to trigger accidentally withkubectl apply -f <dir>/, since alphabetical order isclusterwatchrule.yaml,gitprovider.yaml,gittarget.yaml, but typical "namespace-scoped first" tooling reverses that, and explicit ordering scripts may also get it wrong.) - Observe the
Readystatus on both objects. - Now apply the matching
GitProvider. - Watch the
Readystatus on the GitTarget and ClusterWatchRule for the next ~15–30 seconds.
Expected behavior
When the GitProvider lands, GitTarget should be re-enqueued immediately and flip to Ready=True. Once the GitTarget is Ready, ClusterWatchRule should be re-enqueued immediately and flip to Ready=True as well. End-to-end this should take seconds, not minutes.
Actual behavior
GitProvider/cozystack-example:Ready=True, "Repository connectivity validated".GitTarget/cozystack-example: staysReady=False,reason=ProviderNotFound, messageReferenced GitProvider 'gitops-reverser/cozystack-example' not found— even though the GitProvider is right there.ClusterWatchRule/flux-helmreleases: staysReady=False, messageGitProvider 'gitops-reverser/cozystack-example' (from GitTarget) not found.
The state self-heals on the controllers' periodic requeue (RequeueShortInterval = 2 * time.Minute), or instantly if the dependents are nudged with a no-op edit (e.g. kubectl annotate … reverser.test/nudge=$(date +%s)).
Root cause
Neither dependent controller registers a Watches(...) on the kind it references.
internal/controller/gittarget_controller.go:
func (r *GitTargetReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&configbutleraiv1alpha1.GitTarget{}).
Watches(&corev1.Secret{}, handler.EnqueueRequestsFromMapFunc(r.encryptionSecretToGitTargets)).
Named("gittarget").
Complete(r)
}The only auxiliary watch is on Secret, used for encryption material — not on GitProvider.
internal/controller/clusterwatchrule_controller.go:
func (r *ClusterWatchRuleReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&configbutleraiv1alpha1.ClusterWatchRule{}).
Named("clusterwatchrule").
Complete(r)
}No auxiliary watches at all.
RequeueShortInterval is defined as 2 minutes in internal/controller/constants.go:43, which is the upper bound on the perceived "stuck" time.
Proposed fix
Add a Watches(...) on the referenced kind to each dependent controller, with a map function that enqueues every dependent that names the changed object:
// GitTargetReconciler.SetupWithManager
.Watches(
&configbutleraiv1alpha1.GitProvider{},
handler.EnqueueRequestsFromMapFunc(r.gitProviderToGitTargets),
)
// gitProviderToGitTargets: list all GitTargets in the GitProvider's namespace
// whose spec.providerRef.name == gp.Name, return them as reconcile.Requests.// ClusterWatchRuleReconciler.SetupWithManager
.Watches(
&configbutleraiv1alpha1.GitTarget{},
handler.EnqueueRequestsFromMapFunc(r.gitTargetToClusterWatchRules),
)
// gitTargetToClusterWatchRules: list every ClusterWatchRule whose
// spec.targetRef.{name,namespace} matches the GitTarget; return them as
// reconcile.Requests.This is the standard controller-runtime pattern for "I depend on a sibling kind"; the map function indexes dependents by their reference instead of brute-force listing on every event. Same idea as the existing encryptionSecretToGitTargets map on GitTargetReconciler.
After this change the periodic 2-minute requeue can stay as a safety net but is no longer the primary recovery path for this case — an arriving dependency triggers re-reconcile within tens of milliseconds.
Why it matters
This is the kind of issue that wastes ~2 minutes every fresh-cluster install (and probably every CI run) on Ready=False ProviderNotFound, even when nothing is actually wrong. It also points operators at the wrong suspect — they look at the GitProvider, see it's Ready, then doubt the install.
A practical workaround on our side is to apply the manifests in dependency order in the install script and kubectl wait --for=condition=Ready on the GitProvider before applying the rest. That works, but it shouldn't be required.
Environment
- Kubernetes: v1.36.0 (Talos, Cozystack 1.4.0 platform)
- GitOps Reverser: chart/app 0.24.0 (
ghcr.io/configbutler/charts/gitops-reverser:0.24.0, image0.24.0) - Same code path was present in 0.23.0 (didn't change between 0.23.0 → 0.24.0 release).
Logs
Controller reconcile of ClusterWatchRule/flux-helmreleases, after the GitProvider has been created and is already Ready=True. Note the resourceVersion is up-to-date but the message is still "not found":
{"logger":"ClusterWatchRuleReconciler","msg":"Starting ClusterWatchRule validation",
"ClusterWatchRule":{"name":"flux-helmreleases"},
"target":{"group":"configbutler.ai","kind":"GitTarget","name":"cozystack-example","namespace":"gitops-reverser"},
"generation":1,"resourceVersion":"201991"}
{"logger":"updateStatusWithRetry","msg":"Status update successful","ClusterWatchRule":{"name":"flux-helmreleases"}}
A subsequent no-op kubectl annotate gittarget cozystack-example reverser.test/nudge=$(date +%s) (and the same on the ClusterWatchRule) re-enqueues them and they immediately flip to Ready=True.
Activity
fix: watch for changes in dependent resources