Summary
Found by CodeRabbit on #711. The same-namespace ReleaseBinding collection in the Target reconciler only accepts an empty targetRef.namespace, while the render-registry mapping treats both empty and the target's own namespace as local. So a ReleaseBinding that sets spec.targetRef.namespace explicitly to the target's own namespace falls through the cracks: the same-ns path skips it (namespace is not "") and the cross-ns path needs a ReferenceGrant which nobody creates for the own namespace. Result -> the Target can report no ReleaseBindings even though a perfectly valid one exists.
This is pre-existing behavior. It already was == "" before the ObjectReference refactor, #711 only renamed the field and keeps behavior 1:1 on purpose, so this is the follow-up to normalize it.
Where
pkg/controller/target_controller.go:268 -> same-ns filter, empty only:
if rb.Spec.TargetRef.Name == target.Name && rb.Spec.TargetRef.Namespace == "" {
pkg/controller/target_controller.go:1165 -> the consistent one (registry mapping):
(t.Spec.RenderRegistryRef.Namespace == "" || t.Spec.RenderRegistryRef.Namespace == reg.Namespace)
pkg/controller/target_controller.go:1434 -> the cross-ns collect uses rb.Spec.TargetRef.Namespace != target.Namespace, needs a look too so we dont double count a binding that sets its own namespace explicitly.
What needs to change
Notes
How to reproduce
Truly minimal, no render stack needed here (no ComponentVersion, no working Secret, no actual rendering). The bug already shows up at binding-collection, so 3 objects in one namespace are enough.
- Local cluster with SolAr ->
make dev-cluster and deploy solar the usual dev way.
- Apply these into one namespace
demo. The Registry only needs solarSecretRef set so the Target gets past registry-resolution, the Secret itself does not need to exist, and the Release does not need to exist either:
apiVersion: solar.opendefense.cloud/v1alpha1
kind: Registry
metadata:
name: reg
namespace: demo
spec:
hostname: example.local:5000
solarSecretRef:
name: dummy # only needs to be non-nil, the Secret itself can be absent
---
apiVersion: solar.opendefense.cloud/v1alpha1
kind: Target
metadata:
name: cluster-1
namespace: demo
spec:
renderRegistryRef:
name: reg
---
apiVersion: solar.opendefense.cloud/v1alpha1
kind: ReleaseBinding
metadata:
name: repro-explicit-ns
namespace: demo
spec:
targetRef:
name: cluster-1
namespace: demo # <- explicit, same ns as the binding itself
releaseRef:
name: any-release # does not need to exist
- Look at the Target condition:
kubectl get target cluster-1 -n demo \
-o jsonpath='{range .status.conditions[?(@.type=="ReleasesRendered")]}{.reason}{"\n"}{end}'
Actual: NoReleaseBindings, the binding is dropped even though it sits right there.
Expected: the Target sees the binding.
Now remove the namespace: demo line under targetRef (or leave it empty) and re-apply -> the reason flips away from NoReleaseBindings, the Target now moves on to resolving the Release. That flip is the whole bug, and you never needed a real render pipeline to see it.
Summary
Found by CodeRabbit on #711. The same-namespace
ReleaseBindingcollection in the Target reconciler only accepts an emptytargetRef.namespace, while the render-registry mapping treats both empty and the target's own namespace as local. So aReleaseBindingthat setsspec.targetRef.namespaceexplicitly to the target's own namespace falls through the cracks: the same-ns path skips it (namespace is not"") and the cross-ns path needs aReferenceGrantwhich nobody creates for the own namespace. Result -> the Target can report noReleaseBindingseven though a perfectly valid one exists.This is pre-existing behavior. It already was
== ""before theObjectReferencerefactor, #711 only renamed the field and keeps behavior 1:1 on purpose, so this is the follow-up to normalize it.Where
pkg/controller/target_controller.go:268-> same-ns filter, empty only:pkg/controller/target_controller.go:1165-> the consistent one (registry mapping):pkg/controller/target_controller.go:1434-> the cross-ns collect usesrb.Spec.TargetRef.Namespace != target.Namespace, needs a look too so we dont double count a binding that sets its own namespace explicitly.What needs to change
Namespace == "" || Namespace == target.Namespace, like the registry mapping already doestargetRef.namespace == target.Namespace(dedup)Notes
How to reproduce
Truly minimal, no render stack needed here (no ComponentVersion, no working Secret, no actual rendering). The bug already shows up at binding-collection, so 3 objects in one namespace are enough.
make dev-clusterand deploy solar the usual dev way.demo. TheRegistryonly needssolarSecretRefset so the Target gets past registry-resolution, the Secret itself does not need to exist, and theReleasedoes not need to exist either:kubectl get target cluster-1 -n demo \ -o jsonpath='{range .status.conditions[?(@.type=="ReleasesRendered")]}{.reason}{"\n"}{end}'Actual:
NoReleaseBindings, the binding is dropped even though it sits right there.Expected: the Target sees the binding.
Now remove the
namespace: demoline undertargetRef(or leave it empty) and re-apply -> the reason flips away fromNoReleaseBindings, the Target now moves on to resolving the Release. That flip is the whole bug, and you never needed a real render pipeline to see it.