Add PermissionClaims - #304
Conversation
WalkthroughAdds PermissionClaim types and schema to CRDs and SDK; converts unstructured schema flows to typed BoundSchema/ExportedSchemas; introduces a thread-safe context store and per-claim controllers; propagates permissionClaims through controllers, RBAC, UI, and e2e tests. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant User
participant Provider
participant Controller as ServiceExportController
participant Store as ContextStore
participant Informers as DynamicInformers
participant Consumer
User->>Provider: Create APIServiceExport(spec.permissionClaims)
Provider-->>Controller: Watch APIServiceExport event
Controller->>Store: Set export context (NewKey)
Controller->>Controller: ensureControllers(export)
par Start per-export controllers
Controller->>Informers: Start per-bound-schema controllers
Controller->>Informers: Start per-permission-claim controllers
end
Informers-->>Controller: Event: consumer object ADD
Controller->>Controller: IsClaimed(selector,obj)
alt needs APIServiceNamespace
Controller->>Provider: Create APIServiceNamespace (ownerRef=APIServiceExport)
Controller->>Consumer: Notify downstream reconciliation
end
Controller->>Provider: Reconcile provider/consumer objects (owner-based sync)
sequenceDiagram
autonumber
participant Req as APIServiceExportRequest
participant Ctrl as ServiceExportRequestReconciler
participant API as API Server
participant Schemas as ExportedSchemas
Req->>Ctrl: Reconcile()
Ctrl->>API: List APIResourceSchemas (getExportedSchemas)
API-->>Schemas: ExportedSchemas map
Ctrl->>Ctrl: validate(permissionClaims, scopes)
alt valid
Ctrl->>API: Ensure BoundSchemas via client
Ctrl->>API: Create/Update APIServiceExport (propagate permissionClaims)
else invalid
Ctrl-->>Req: Update status / return error
end
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)📓 Common learnings🧬 Code graph analysis (1)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
🔇 Additional comments (6)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
fa35d43 to
b0f25c4
Compare
da67d1a to
b3cd33f
Compare
b3cd33f to
59d56ce
Compare
There was a problem hiding this comment.
Actionable comments posted: 18
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
backend/controllers/servicenamespace/servicenamespace_controller.go (1)
233-246: Cache NotFound may be stale; double‑check live before deleting namespace.Switching to cache.Get introduces eventual‑consistency races. You may delete the derived namespace while APIServiceNamespace still exists.
Apply this diff to confirm with the live client before deleting:
- if err := cache.Get(ctx, req.NamespacedName, apiServiceNamespace); err != nil { + if err := cache.Get(ctx, req.NamespacedName, apiServiceNamespace); err != nil { if errors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Handle deletion logic here - nsName := req.Namespace + "-" + req.Name - if err := r.reconciler.deleteNamespace(ctx, client, nsName); err != nil && !errors.IsNotFound(err) { - return ctrl.Result{}, fmt.Errorf("failed to delete namespace %q: %w", nsName, err) - } - logger.Info("APIServiceNamespace not found, ignoring") - return ctrl.Result{}, nil + // Double-check with live client to avoid acting on a stale cache. + live := &kubebindv1alpha2.APIServiceNamespace{} + if err2 := client.Get(ctx, req.NamespacedName, live); err2 != nil { + if errors.IsNotFound(err2) { + // Confirmed deleted, proceed with cleanup. + nsName := req.Namespace + "-" + req.Name + if err := r.reconciler.deleteNamespace(ctx, client, nsName); err != nil && !errors.IsNotFound(err) { + return ctrl.Result{}, fmt.Errorf("failed to delete namespace %q: %w", nsName, err) + } + logger.Info("APIServiceNamespace not found, ignoring") + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("failed to get APIServiceNamespace live: %w", err2) + } + // Live object exists; requeue and let cache catch up. + return ctrl.Result{Requeue: true}, nil } // Error reading the object - requeue the request. return ctrl.Result{}, fmt.Errorf("failed to get APIServiceNamespace: %w", err) }backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (2)
139-166: Don’t skip BoundSchema creation when versions are empty.Empty versions are allowed (provider picks defaults). Skipping here can lead to BoundSchemaNotFound later.
- if len(res.Versions) == 0 { - continue - }Optional: replace the nested scan with a direct lookup by key to avoid O(N*M). Example:
key := res.ResourceGroupName() if bs, ok := exportedSchemas[key]; ok { // apply namespace/scope, ensure/create }
254-257: Avoid clobbering Succeeded with Failed based on age.Currently a >1m-old request is marked Failed even after setting Succeeded.
- if time.Since(req.CreationTimestamp.Time) > time.Minute { + if time.Since(req.CreationTimestamp.Time) > time.Minute && + req.Status.Phase != kubebindv1alpha2.APIServiceExportRequestPhaseSucceeded { req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseFailed req.Status.TerminalMessage = conditions.GetMessage(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) }pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
56-66: Validate constructor input to enforce non‑nil apiServiceExportFail fast if
apiServiceExportis nil to uphold reconcile invariants and avoid runtime panics.func NewController( - apiServiceExport *kubebindv1alpha2.APIServiceExport, // used to establish owner references when create happens from the consumer side. + apiServiceExport *kubebindv1alpha2.APIServiceExport, // used to establish owner references when create happens from the consumer side. gvr schema.GroupVersionResource, @@ ) (*controller, error) { + if apiServiceExport == nil { + return nil, fmt.Errorf("apiServiceExport must not be nil") + }kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (1)
319-345: boundSchemas: group/resource patterns OK — lister uses singular resource name and must be fixed
- Verified: ResourceGroupName() builds "." (sdk/apis/kubebind/v1alpha2/boundchema_types.go:52), so the YAML's separate
resourceandgroupfields and their regexes align with how schema names are formed.- Action required: generated lister calls kubebindv1alpha2.Resource("boundschema") (sdk/client/listers/kubebind/v1alpha2/boundschema.go:46). This should be plural ("boundschemas") per client-go conventions — matches a known lister-gen generator issue and can break cache lookups. Update the generator/config or rename the resource string.
🧹 Nitpick comments (47)
kcp/deploy/examples/sheriff.yaml (1)
4-6: Add trailing newline; verify CRD supports spec.intent.Fix the lint error by ending the file with a newline. Also ensure Sheriff’s schema includes spec.intent; otherwise kubectl apply will fail.
Makefile (1)
117-117: LGTM; optional: make the dex exclusion segment-safe.Current grep -v ./dex works; to avoid accidental matches, consider anchoring on the path segment.
Apply this diff:
-GOMODS := $(shell find . -name 'go.mod' -exec dirname {} \; | grep -v hack/tools | grep -v ./dex) +GOMODS := $(shell find . -name 'go.mod' -exec dirname {} \; | grep -v hack/tools | grep -vE '(^|/)\.*/?dex(/|$$)')sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
30-33: Fix key‑format comment to match implementation.The map is keyed by resource.group (plural.group), not resource.version.group.
Apply this diff:
-// ExportedSchemas are the schemas exported by the current backend. -// Keys are resource.version.group string for quick resolve. +// ExportedSchemas are the schemas exported by the current backend. +// Keys are "<resource>.<group>" (i.e., plural.group) for quick resolve. type ExportedSchemas map[string]*BoundSchema
49-55: Clarify docstring: it returns resource.group, not just the group name.Align the comment with the return value and cross‑references.
Apply this diff:
-// ResourceGroupName returns the group name of the resource. +// ResourceGroupName returns "<resource>.<group>" (plural.group). // -// Important: If you change this, change one for APIServiceExportRequestResource too. +// Important: If you change this, update the corresponding key builders (e.g., APIServiceExportRequestResource).backend/http/handler.go (2)
383-399: Deterministic listing + use a served version (if available).
- Map iteration order is random; sort results for a stable UI.
- Pick a served version instead of blindly taking index 0.
- Minor: use GetName consistently.
- for _, item := range exportedSchemas { + for _, item := range exportedSchemas { if len(item.Spec.Versions) == 0 { - logger.Error(fmt.Errorf("no versions found"), "skipping schema", "name", item.Name) + logger.Error(fmt.Errorf("no versions found"), "skipping schema", "name", item.GetName()) continue } + // Prefer a served version if present; otherwise fallback to first. + ver := item.Spec.Versions[0].Name + for _, v := range item.Spec.Versions { + if v.Served { + ver = v.Name + break + } + } result = append(result, UISchema{ Name: item.GetName(), Kind: item.Spec.Names.Kind, Scope: string(item.Spec.Scope), - Version: item.Spec.Versions[0].Name, + Version: ver, Group: item.Spec.Group, // Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it. Resource: item.Spec.Names.Plural, SessionID: sessionID, }) }Add sorting before rendering:
- bs := bytes.Buffer{} + // Stable ordering for UI. + sort.Slice(result, func(i, j int) bool { + if result[i].Group != result[j].Group { + return result[i].Group < result[j].Group + } + if result[i].Resource != result[j].Resource { + return result[i].Resource < result[j].Resource + } + return result[i].Version < result[j].Version + }) + bs := bytes.Buffer{}Also import sort:
import ( @@ "time" + "sort"
527-556: Don’t fail the whole list on one bad item; improve error context.
- Return a more accurate list error.
- Skip malformed items with a log instead of aborting.
-func (h *handler) getBackendDynamicResource(ctx context.Context, cluster string) (kubebindv1alpha2.ExportedSchemas, error) { +func (h *handler) getBackendDynamicResource(ctx context.Context, cluster string) (kubebindv1alpha2.ExportedSchemas, error) { + logger := klog.FromContext(ctx) @@ - items, err := h.kubeManager.ListDynamicResources(ctx, cluster, gvk, labelSelector.AsSelector()) + items, err := h.kubeManager.ListDynamicResources(ctx, cluster, gvk, labelSelector.AsSelector()) if err != nil { - return nil, fmt.Errorf("failed to list crds: %w", err) + return nil, fmt.Errorf("failed to list dynamic resources for %s: %w", gvk.String(), err) } var boundSchemas kubebindv1alpha2.ExportedSchemas = make(map[string]*kubebindv1alpha2.BoundSchema, len(items.Items)) for _, item := range items.Items { boundSchema, err := helpers.UnstructuredToBoundSchema(item) if err != nil { - return nil, err + logger.Error(err, "skipping invalid exported schema", "gvk", gvk.String(), "name", item.GetName()) + continue } boundSchemas[boundSchema.ResourceGroupName()] = boundSchema } return boundSchemas, nilbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (4)
56-66: Validate first to avoid creating objects for invalid requests.Run validate before ensureBoundSchemas; prevents writing BoundSchemas when the request will be rejected.
- // We must ensure schemas are created in form of boundSchemas first for the validation. - // Worst case scenario if validation fails, we will reuse schemas for same consumer once issues are fixed. - if err := r.ensureBoundSchemas(ctx, cl, cache, req); err != nil { - conditions.SetSummary(req) - return err - } - - if err := r.validate(ctx, cl, req); err != nil { + // Validate early to avoid creating objects for invalid requests. + if err := r.validate(ctx, cl, req); err != nil { conditions.SetSummary(req) return err } + if err := r.ensureBoundSchemas(ctx, cl, cache, req); err != nil { + conditions.SetSummary(req) + return err + }
88-130: Be tolerant to conversion errors when listing exported schemas.Skip bad items with a log; don’t fail the whole reconcile.
-func (r *reconciler) getExportedSchemas(ctx context.Context, cl client.Client) (kubebindv1alpha2.ExportedSchemas, error) { +func (r *reconciler) getExportedSchemas(ctx context.Context, cl client.Client) (kubebindv1alpha2.ExportedSchemas, error) { + logger := klog.FromContext(ctx) @@ for _, item := range list.Items { boundSchema, err := helpers.UnstructuredToBoundSchema(item) if err != nil { - return nil, err + logger.Error(err, "skipping invalid exported schema", "kind", gvk.Kind, "name", item.GetName()) + continue } boundSchemas[boundSchema.ResourceGroupName()] = boundSchema }
261-263: Fix klog Info usage (no printf formatting in msg).- logger.Info("Deleting service binding request %s/%s", req.Namespace, req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time)) + logger.Info("Deleting service binding request", "namespace", req.Namespace, "name", req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time))
324-335: Use the request’s condition type when rejecting invalid permission claims.Use APIServiceExportRequestConditionExportsReady for consistency (you already use it elsewhere in validate).
- req, - kubebindv1alpha2.APIServiceExportConditionPermissionClaim, + req, + kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, "InvalidPermissionClaim",sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
92-95: Model PermissionClaims as a map-list for SSA-friendly merges and dedupeWithout listType metadata, server-side apply treats the slice atomically. Key by Group/Resource to ensure stable merges and uniqueness.
- // PermissionClaims records decisions about permission claims requested by the service provider. - // Access is granted per GroupResource. - PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` + // PermissionClaims records decisions about permission claims requested by the service provider. + // Access is granted per GroupResource. + // +listType=map + // +listMapKey=group + // +listMapKey=resource + PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`Optional: consider immutability once connected (e.g., XValidation guarding changes) if that matches your lifecycle.
pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (1)
36-36: Nil safety: document/guard apiServiceExport invariant
apiServiceExportis dereferenced later for an OwnerRef. If it can be nil, reconcile will panic. Either guarantee non‑nil by constructor or guard at use.pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
103-104: Avoid holding a potentially stale pointerStoring the whole
*APIServiceExportcan drift (e.g., UID after recreate). Consider storing only{Name, UID}or resolving fresh from the lister when needed.pkg/indexers/servicebinding.go (2)
28-29: Add doc comment for exported constantAdd a brief comment to satisfy linters and aid discoverability.
- ByAPIServiceBindingCRD = "byCRD" + // ByAPIServiceBindingCRD indexes APIServiceBinding by CRD name "resource.group". + ByAPIServiceBindingCRD = "byCRD"
44-56: Small tidy: build key without fmt and align with canonical namingUse string concat for less overhead and keep format consistent with other helpers (e.g., ResourceGroupName()).
- keys := make([]string, 0, len(binding.Status.BoundSchemas)) - for _, bound := range binding.Status.BoundSchemas { - keys = append(keys, fmt.Sprintf("%s.%s", bound.Resource, bound.Group)) - } + keys := make([]string, 0, len(binding.Status.BoundSchemas)) + for _, bound := range binding.Status.BoundSchemas { + keys = append(keys, bound.Resource+"."+bound.Group) + }kcp/deploy/examples/apiserviceexport-cluster.yaml (1)
11-17: YAML nit: show core explicitly and add commentEmpty apiGroup ("") is correct for core. Consider adding a brief comment for users skimming the example.
permissionClaims: - - apiGroup: "" + - apiGroup: "" # core API group resource: configmapskcp/deploy/examples/apiserviceexport-namespaced.yaml (1)
11-17: Optional doc hintAdd a brief note that the labelSelector limits which objects are claimable.
selector: - labelSelector: + labelSelector: # only objects with these labels are claimable matchLabels: app: wildwestsdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (2)
41-43: Condition granularity for multiple claimsOne PermissionClaim condition type can’t represent per-claim outcomes. If you need per‑G/R reporting, consider per‑item status or include G/R in Reason.
55-56: Printer column "Established" likely staleNo Established condition is defined here. Consider switching the printer column to ConsumerInSync or remove it.
-// +kubebuilder:printcolumn:name="Established",type="string",JSONPath=`.status.conditions[?(@.type=="Established")].status`,priority=5 +// +kubebuilder:printcolumn:name="ConsumerInSync",type="string",JSONPath=`.status.conditions[?(@.type=="ConsumerInSync")].status`,priority=5kcp/README.md (3)
15-17: Fix fenced code block languages (MD040)Add a language to these blocks.
-``` +```text :root:kube-bind-
+text
:root:kube-bind/apiexport/kube-bind.io-``` +```bash kubectl apply -f kcp/deploy/examples/cowboy.yaml kubectl apply -f kcp/deploy/examples/sheriff.yamlAlso applies to: 19-21, 123-127 --- `137-139`: **Shell quoting for JSONPath** Use single quotes to avoid nested double-quote pitfalls. ```diff -k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath="{.status.endpoints[0].url}")/clusters/*" get crd +k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath='{.status.endpoints[0].url}')/clusters/*" get crd
140-147: Example completenessConsider adding an example Secret/ServiceAccount to demonstrate multiple claim types end‑to‑end.
sdk/apis/kubebind/v1alpha2/claimable_apis.go (3)
37-81: Exported mutable slice invites accidental mutationMake the slice unexported and expose a getter that returns a copy.
-var ClaimableAPIs = []InternalAPI{ +var claimableAPIs = []InternalAPI{ @@ -} +} + +func GetClaimableAPIs() []InternalAPI { return append([]InternalAPI(nil), claimableAPIs...) }
83-90: Return error with contextInclude group/resource in the error.
- return schema.GroupVersionResource{}, fmt.Errorf("no matching API found") + return schema.GroupVersionResource{}, fmt.Errorf("no matching API for %q/%q", claim.Group, claim.Resource)
83-90: Optional: O(1) lookupPrecompute a map[group/resource]GVR for faster resolution.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
191-216: Fix ByIndex error handling and log messageByIndex errors aren’t k8s API NotFound; drop errors.IsNotFound and fix the stray “secret” comment. Also clarify var name.
-func (c *controller) enqueueCRD(logger klog.Logger, obj any) { - name, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) +func (c *controller) enqueueCRD(logger klog.Logger, obj any) { + crdName, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) return } - exports, err := c.serviceExportIndexer.ByIndex(indexers.ServiceExportByCustomResourceDefinition, name) - if err != nil && !errors.IsNotFound(err) { - runtime.HandleError(err) - return - } else if errors.IsNotFound(err) { - return // skip this secret - } + exports, err := c.serviceExportIndexer.ByIndex(indexers.ServiceExportByCustomResourceDefinition, crdName) + if err != nil { + runtime.HandleError(err) + return + } for _, obj := range exports { export := obj.(*kubebindv1alpha2.APIServiceExport) key, err := cache.MetaNamespaceKeyFunc(export) if err != nil { runtime.HandleError(err) return } - logger.V(2).Info("queueing APIServiceExport", "key", key, "reason", "CustomResourceDefinition", "name", name) + logger.V(2).Info("queueing APIServiceExport", "key", key, "reason", "CustomResourceDefinition", "crd", crdName) c.queue.Add(key) } }
299-305: NotFound shouldn’t be logged as errorDowngrade to V(2) to avoid noise in normal delete flows.
-logger.Error(err, "APIServiceExport disappeared") +logger.V(2).Info("APIServiceExport disappeared", "err", err)test/e2e/bind/happy-case_test.go (1)
140-176: Injecting PermissionClaims into the request stdin is correct; minor nit: validate before marshal.Optional: assert request.Spec.PermissionClaims was empty before overwrite to avoid accidental merging semantics changing later.
- request.Spec.PermissionClaims = []kubebindv1alpha2.PermissionClaim{ + require.Empty(t, request.Spec.PermissionClaims) + request.Spec.PermissionClaims = []kubebindv1alpha2.PermissionClaim{pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (3)
105-114: Avoid panic on provider informer Get by asserting type.GetterInformer may return runtime.Object of unexpected type; guard the cast.
- return obj.(*unstructured.Unstructured), nil + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("unexpected object type %T", obj) + } + return u, nil
251-285: Key mismatch in log vs. enqueue; remove unused consumer key.We enqueue upstreamKey but log the consumer key; fix log to upstreamKey and drop dead code.
- for _, obj := range sns { - sn := obj.(*kubebindv1alpha2.APIServiceNamespace) - if sn.Namespace == c.providerNamespace { - key := fmt.Sprintf("%s/%s", sn.Name, name) - logger.V(2).Info("queueing Unstructured", "key", key) - c.queue.Add(upstreamKey) - return - } - } + for _, obj := range sns { + sn := obj.(*kubebindv1alpha2.APIServiceNamespace) + if sn.Namespace == c.providerNamespace { + logger.V(2).Info("queueing Unstructured", "key", upstreamKey) + c.queue.Add(upstreamKey) + return + } + }
217-218: Minor: logged key label says gvr but uses GVK.Align log key or value to avoid confusion.
- logger.V(2).Info("queueing consumer object", "gvr", o.GroupVersionKind().String(), ... + logger.V(2).Info("queueing consumer object", "gvk", o.GroupVersionKind().String(), ...backend/controllers/servicenamespace/servicenamespace_reconcile.go (3)
99-119: Fix log/error messages: it's a ClusterRole, not Role.Minor correctness/readability tweak.
- return fmt.Errorf("failed to get Role %s: %w", name, err) + return fmt.Errorf("failed to get ClusterRole %s: %w", name, err)…and similar replacements for create/update error messages in this block.
155-166: Shadowed variable ‘role’; rename to avoid confusion.No behavior change, improves clarity.
- if role == nil { - role := &rbacv1.Role{ + if role == nil { + newRole := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: sns.Status.Namespace }, - Rules: permissions, + Rules: permissions, } - if err := client.Create(ctx, role); err != nil { + if err := client.Create(ctx, newRole); err != nil { ...
173-203: Fix RoleBinding log/error strings and consider update path.Message says “Role” while operating on RoleBinding; implement update similar to Role to handle subject/roleref drift.
- return fmt.Errorf("failed to get Role %s: %w", name, err) + return fmt.Errorf("failed to get RoleBinding %s: %w", name, err) ... - logger.Info("Role already exists, update not implemented.", "name", name) + logger.Info("RoleBinding already exists; update not implemented.", "name", name)Optional: add compare+update for RoleBinding like ensureRBACRoleBinding does.
deploy/crd/kube-bind.io_apiservicebindings.yaml (1)
230-317: CRD schema for permissionClaims looks good; add list semantics.Consider marking permissionClaims as atomic to simplify patch/merge behavior.
- permissionClaims: + permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: ... type: array + x-kubernetes-list-type: atomicpkg/konnector/controllers/contextstore/contextstore.go (3)
46-49: Use RWMutex to reduce read contentionGet/List are read-heavy; switch to RWMutex and use RLock for reads.
type contextStore struct { - lock sync.Mutex + lock sync.RWMutex store map[Key]SyncContext } func (c *contextStore) Get(key Key) (SyncContext, bool) { - c.lock.Lock() - defer c.lock.Unlock() + c.lock.RLock() + defer c.lock.RUnlock() val, ok := c.store[key] return val, ok } func (c *contextStore) ListPrefixed(prefix Key) []SyncContext { - c.lock.Lock() - defer c.lock.Unlock() + c.lock.RLock() + defer c.lock.RUnlock() var results []SyncContext for k, v := range c.store { if strings.HasPrefix(k.String(), prefix.String()) { results = append(results, v) } } return results } func (c *contextStore) Set(key Key, value SyncContext) { c.lock.Lock()Also applies to: 67-72, 74-85, 87-92
78-85: Make prefix matching boundary-awareAvoid accidental matches like "ns.a" matching "ns.ab". Respect component boundaries.
- for k, v := range c.store { - if strings.HasPrefix(k.String(), prefix.String()) { + ps := prefix.String() + for k, v := range c.store { + ks := k.String() + if ks == ps || strings.HasPrefix(ks, ps+".") { results = append(results, v) } }
19-22: Nit: grammar“Context are stored…” → “Contexts are stored…”.
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (5)
118-126: Wrong namespace in log for downstream creationLog prints providerNS as downstreamNamespace; use consumerNS.
- logger.Info("Creating missing downstream object", "downstreamNamespace", providerNS, "downstreamName", providerObj.GetName()) + logger.Info("Creating missing downstream object", "downstreamNamespace", consumerNS, "downstreamName", providerObj.GetName())
149-155: Log message mismatchMessage says “deleting downstream object” but you delete upstream. Tweak for clarity.
- logger.Info("Owner copy of the object is gone, deleting downstream object", "name", name, "namespace", providerNS) + logger.Info("Owner copy of the object is gone, deleting upstream object", "name", name, "namespace", providerNS)
69-74: Use consistent log keys for namespacesPrefer "upstreamNamespace"/"downstreamNamespace" across controllers to aid log filtering.
- logger = logger.WithValues("name", name, "providerNamespace", providerNS) + logger = logger.WithValues("name", name, "upstreamNamespace", providerNS) @@ - logger = logger.WithValues("providerNamespace", sn.Status.Namespace) + logger = logger.WithValues("upstreamNamespace", sn.Status.Namespace)Also applies to: 51-52
207-215: Duplicate SetNamespace callSetNamespace(downstreamNS) is called twice.
candidate.SetOwnerReferences(nil) candidate.SetFinalizers(nil) - candidate.SetNamespace(downstreamNS) candidate.SetCreationTimestamp(v1.Time{})
174-178: Variable shadowing nitproviderObj is reassigned to a different meaning; use a new name (e.g., currentProvider) to reduce cognitive load.
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
163-166: String() for core group yields trailing dotConsider returning resource or resource.core (to align with ResourceGroupName()) when Group=="". Otherwise logs show “pods.”.
func (r GroupResource) String() string { - return fmt.Sprintf("%s.%s", r.Resource, r.Group) + if r.Group == "" { + return r.Resource + } + return fmt.Sprintf("%s.%s", r.Resource, r.Group) }pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (3)
89-90: Verify the export key construction pattern.The key construction using
namespace + "." + nameshould use thecontextstore.NewKeyhelper for consistency.- exportKey := contextstore.Key(namespace + "." + name) // Key for the export + exportKey := contextstore.NewKey(namespace, name) // Key for the export
406-409: Improve error context in claim controller setup.The generic "aborting" log message could be more descriptive about the failure context.
- logger.Info("aborting", "error", err) + logger.Error(err, "failed to create provider informer for permission claim", "claim", claim.Resource, "gvr", claimGVR)
531-534: Fix plural inconsistency in error message.The error message refers to "BoundSchemas" but the condition reason uses singular form in other places.
- "BoundSchemasNotValid", + "BoundSchemasNotReady", conditionsapi.ConditionSeverityWarning, - "One or more BoundSchemas are not valid", + "One or more BoundSchemas are not ready",
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (37)
Makefile(1 hunks)backend/controllers/clusterbinding/clusterbinding_reconcile.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(4 hunks)backend/controllers/servicenamespace/servicenamespace_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(4 hunks)backend/http/handler.go(4 hunks)backend/kubernetes/resources/namespace.go(1 hunks)cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go(1 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)kcp/README.md(6 hunks)kcp/deploy/bootstrap.go(1 hunks)kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)kcp/deploy/examples/apiserviceexport-namespaced.yaml(1 hunks)kcp/deploy/examples/cowboy.yaml(1 hunks)kcp/deploy/examples/sheriff.yaml(1 hunks)kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)pkg/indexers/servicebinding.go(2 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(1 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go(2 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(2 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go(2 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)sdk/apis/kubebind/v1alpha2/boundchema_types.go(4 hunks)sdk/apis/kubebind/v1alpha2/claimable_apis.go(1 hunks)sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go(8 hunks)test/e2e/bind/happy-case_test.go(5 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-12T08:40:15.272Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.272Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gokcp/deploy/resources/apiexport-kube-bind.io.yamlbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.gokcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlkcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlkcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
📚 Learning: 2025-09-12T09:05:29.743Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.743Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-12T08:55:41.816Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.816Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.go
🧬 Code graph analysis (30)
backend/controllers/clusterbinding/clusterbinding_reconcile.go (3)
sdk/apis/kubebind/v1alpha2/register.go (1)
GroupName(32-32)backend/kubernetes/resources/rbac.go (1)
EnsureBinderClusterRole(56-139)sdk/client/clientset/versioned/typed/kubebind/v1alpha2/boundschema.go (1)
Create(40-52)
backend/kubernetes/resources/namespace.go (1)
backend/kubernetes/manager.go (1)
m(80-150)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
backend/controllers/serviceexport/serviceexport_reconcile.go (1)
r(49-90)
backend/controllers/servicenamespace/servicenamespace_controller.go (2)
backend/controllers/serviceexport/serviceexport_controller.go (1)
r(100-151)backend/controllers/serviceexportrequest/serviceexportrequest_controller.go (1)
r(188-239)
kcp/deploy/bootstrap.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(169-174)Selector(133-142)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (3)
sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
OwnerProvider(181-181)OwnerConsumer(183-183)Owner(177-177)pkg/konnector/controllers/cluster/serviceexport/status/status_reconcile.go (1)
r(41-101)
kcp/deploy/resources/apiexport-kube-bind.io.yaml (3)
sdk/client/clientset/versioned/typed/kubebind/v1alpha1/apiserviceexport.go (1)
Create(40-52)sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go (1)
APIServiceBindings(26-39)backend/controllers/clusterbinding/clusterbinding_controller.go (1)
mapAPIResourceSchema(223-238)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (4)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(169-174)Selector(133-142)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(48-55)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (2)
backend/controllers/serviceexport/serviceexport_controller.go (1)
r(100-151)pkg/konnector/controllers/cluster/servicebinding/servicebinding_controller.go (1)
NewController(51-153)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (2)
pkg/konnector/controllers/cluster/serviceexport/status/status_reconcile.go (1)
getServiceNamespace(31-38)backend/controllers/serviceexport/serviceexport_controller.go (1)
r(100-151)
sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(169-174)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (6)
sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
Selector(133-142)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(145-161)PermissionClaim(169-174)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportConditionPermissionClaim(42-42)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)backend/controllers/serviceexport/serviceexport_reconcile.go (1)
r(49-90)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (3)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
ConditionType(48-48)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(169-174)sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (4)
AcceptedNames(216-234)Name(150-202)in(73-75)in(69-71)
pkg/indexers/servicebinding.go (3)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (1)
Resource(147-147)pkg/indexers/crd.go (1)
IndexCRDByServiceBinding(15-30)pkg/indexers/serviceexport.go (1)
IndexServiceExportByBoundSchema(40-52)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
backend/controllers/serviceexport/serviceexport_controller.go (1)
r(100-151)backend/controllers/serviceexportrequest/serviceexportrequest_controller.go (1)
r(188-239)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(169-174)
pkg/konnector/controllers/contextstore/contextstore.go (1)
pkg/konnector/konnector_reconcile.go (2)
kubeconfig(45-49)lock(37-43)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (4)
pkg/konnector/controllers/contextstore/contextstore.go (1)
New(57-61)pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/indexers/serviceexport.go (2)
ServiceExportByCustomResourceDefinition(26-26)IndexServiceExportByCustomResourceDefinition(30-37)pkg/konnector/controllers/cluster/servicebinding/servicebinding_controller.go (1)
c(204-222)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
sdk/apis/kubebind/v1alpha1/apiserviceexportrequest_types.go (2)
Group(117-133)GroupResource(108-114)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
test/e2e/bind/happy-case_test.go (4)
test/e2e/framework/clients.go (1)
KubeClient(37-41)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
APIServiceExportRequest(46-60)PermissionClaim(169-174)GroupResource(145-161)Selector(133-142)test/e2e/framework/bind.go (2)
BindAPIService(83-105)Bind(36-75)cli/pkg/kubectl/bind/plugin/bind.go (1)
b(141-307)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (6)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(38-44)Key(28-28)NewKey(34-36)SyncContext(51-55)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(169-174)Selector(133-142)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
Resource(147-147)NewController(52-145)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(48-55)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(51-167)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
Conditions(92-92)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (2)
BoundSchemaToCRD(131-200)CRDToBoundSchema(33-111)
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (4)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(169-174)GroupResource(145-161)Selector(133-142)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
InternalAPI(29-35)sdk/apis/kubebind/v1alpha1/zz_generated.deepcopy.go (5)
in(32-39)in(380-384)in(218-225)in(397-401)in(42-49)
deploy/crd/kube-bind.io_apiservicebindings.yaml (3)
sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (4)
Group(118-147)Name(150-202)OpenAPIV3Schema(204-212)APIServiceExportCRDSpec(82-100)sdk/client/clientset/versioned/typed/kubebind/v1alpha1/apiservicebinding.go (2)
APIServiceBindings(35-37)Create(40-52)sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go (1)
APIServiceBindings(26-39)
deploy/crd/kube-bind.io_apiserviceexports.yaml (1)
sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (4)
AcceptedNames(216-234)Name(150-202)APIServiceExportCRDSpec(82-100)Group(118-147)
kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (2)
sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (4)
Name(150-202)OpenAPIV3Schema(204-212)APIServiceExportCRDSpec(82-100)AcceptedNames(216-234)sdk/apis/kubebind/v1alpha1/helpers/serviceexport.go (1)
CRDToServiceExport(77-124)
deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (2)
sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (2)
Name(150-202)APIServiceExportCRDSpec(82-100)sdk/apis/kubebind/v1alpha1/helpers/serviceexport.go (2)
CRDToServiceExport(77-124)ServiceExportToCRD(34-74)
kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml (2)
sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (4)
APIServiceExportCRDSpec(82-100)OpenAPIV3Schema(204-212)AcceptedNames(216-234)Name(150-202)sdk/apis/kubebind/v1alpha1/helpers/serviceexport.go (2)
ServiceExportToCRD(34-74)CRDToServiceExport(77-124)
kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (1)
sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (2)
Name(150-202)OpenAPIV3Schema(204-212)
🪛 YAMLlint (1.37.1)
kcp/deploy/examples/sheriff.yaml
[error] 6-6: no new line character at the end of file
(new-line-at-end-of-file)
🪛 markdownlint-cli2 (0.17.2)
kcp/README.md
124-124: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: lint
- GitHub Check: verify
- GitHub Check: go-test-e2e
🔇 Additional comments (41)
kcp/deploy/examples/cowboy.yaml (1)
5-7: Confirm Cowboy CRD includes spec.intent and default namespace is intended.If the CRD lacks spec.intent, this example won’t apply. Also verify that moving it into namespace "default" is desired for your docs/tests.
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
21-21: Import fmt is appropriate.
261-267: Receiver rename is fine.Ensure all callers and generated deepcopy code compile against this receiver name (should be transparent).
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
286-291: LGTM — variable rename improves clarity and aligns with ResourceGroupName usage.backend/kubernetes/resources/namespace.go (1)
31-31: Incorrect — no annotation rename detected; ignore migration recommendation.
The codebase still defines resources.IdentityAnnotationKey = "backend.kube-bind.io/identity" and CreateNamespace, IndexNamespacesByIdentity, and the manager list all use that key; there is no LegacyIdentityAnnotationKey or alternate annotation present.Likely an incorrect or invalid review comment.
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (2)
241-241: Propagating PermissionClaims looks good.This correctly mirrors request claims onto the export spec.
277-286: Clarify condition message for empty exported schemas.The message currently repeats the reason code. Use a human-readable sentence.
[raise_nitpick_refactor]
- "SchemaNotFound not found", + "no exported schemas found",deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (1)
224-311: permissionClaims schema addition looks correct and consistent with types.Validation for mutually exclusive selector fields and required resource is in place. No blockers.
kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (1)
5-5: APIResourceSchema v1alpha2 with permissionClaims: LGTM.New version name and permissionClaims surface align with the CRD; storage=true is set. Looks good.
Also applies to: 220-307
backend/controllers/clusterbinding/clusterbinding_reconcile.go (1)
161-165: Allowing boundschemas/status updates is appropriate.Grants get/update/patch on the status subresource; matches controllers that report conditions. No concerns.
kcp/deploy/examples/apiserviceexport-namespaced.yaml (1)
11-17: Mirror apiGroup/group check hereSame concern as cluster example. Keep the field name consistent with the struct/CRD.
See the script in the cluster example comment.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
98-99: Good change: replace ad‑hoc map with contextstoreCentralized sync context management. LGTM.
126-129: Indexer wiring looks rightIndex registered before use; AddIfNotPresentOrDie guards duplicates. LGTM.
kcp/deploy/resources/apiexport-kube-bind.io.yaml (1)
52-52: Referenced APIResourceSchema IDs exist — no action required
Found matching APIResourceSchema resources:
- kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml — name: v250918-7b255ac.apiservicebindings.kube-bind.io
- kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml — name: v250918-7b255ac.apiserviceexports.kube-bind.io
- kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml — name: v250918-7b255ac.apiserviceexportrequests.kube-bind.io
test/e2e/bind/happy-case_test.go (5)
48-59: Good test matrix expansion.Running both with and without permission claims for cluster- and namespace-scoped resources increases coverage meaningfully.
91-93: Core clients wiring looks correct.Direct CoreV1 clients for ConfigMaps/Secrets are appropriate for claim tests.
223-263: Resource creation paths LGTM.Labels match selectors; data types are correct.
266-294: Sync assertions are sound.Existence checks followed by exact data validation is the right order.
297-324: Delete propagation checks LGTM.Using NotFound guards eliminates flakiness.
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
331-358: Startup/shutdown flow LGTM.Workers, typed queue, and dynamic event handler registration look correct.
backend/controllers/servicenamespace/servicenamespace_reconcile.go (1)
251-293: Helper getters/list LGTM.Cache-based reads match controller-runtime patterns.
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (8)
93-96: Binding.Spec deep-copies now include nested fields.Correct change to handle new slices.
153-159: Deep-copying PermissionClaims slice in BindingSpec is correct.
388-394: Deep-copying PermissionClaims in ExportRequestSpec is correct.
441-447: Deep-copying PermissionClaims in ExportSpec is correct.
971-999: ExportedSchemas deep-copy implementation LGTM.Map with pointer values handled safely.
1018-1036: InternalAPI deep copy covers Names and Instance.Good coverage for runtime.Object.
1087-1102: PermissionClaim deep copy is correct.
1105-1123: Selector deep copy handles optional LabelSelector.sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
109-113: Addition of PermissionClaims to spec looks goodField wiring and JSON tags are consistent with related types.
167-175: PermissionClaim struct LGTMInline GroupResource and selector shape match CRD.
176-189: Owner type and constants LGTMString() helper simplifies label comparisons.
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (1)
139-145: Set ResourceVersion on candidate before updateFile: pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (around lines 139–145)
candidateFromOwnerObj clears ResourceVersion; updateConsumerObject likely performs an Update and therefore needs candidate.ResourceVersion. Copy consumerObj.GetResourceVersion() onto candidate before calling updateConsumerObject:
- if !equality.Semantic.DeepEqual(candidate, current) { + if !equality.Semantic.DeepEqual(candidate, current) { + candidate.SetResourceVersion(consumerObj.GetResourceVersion()) logger.Info("Updating downstream object data", "downstreamNamespace", consumerNS, "downstreamName", consumerObj.GetName()) if _, err := r.updateConsumerObject(ctx, candidate); err != nil { logger.Error(err, "error updating consumer object") return err }Attempt to locate updateConsumerObject failed (rg reported "No files were searched"); verify whether updateConsumerObject uses Update (requires ResourceVersion) or Patch and adjust accordingly.
kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (3)
5-5: Version name update looks good.The schema version name update from
v250809-5ed76a1tov250918-7b255acfollows the expected naming convention for tracking schema changes with date and commit hash.
287-287: Good use of atomic typing annotations.The
x-kubernetes-list-type: atomicandx-kubernetes-map-type: atomicannotations correctly prevent server-side apply from attempting strategic merge patches on these fields, which is appropriate for label selectors.Also applies to: 293-293, 303-303
225-312: Validate the XOR validation rule for permission claim selectors.The current rule enforces exactly one of "all" or "labelSelector" but may treat an empty labelSelector as set — check for non-empty matchLabels or matchExpressions instead.
File: kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (lines 225-312; validation at ~306-308)
- - message: either "all" or "labelSelector" must be set - rule: (has(self.all) && self.all) != (has(self.labelSelector) - && size(self.labelSelector) > 0) + - message: either "all" or "labelSelector" must be set + rule: (has(self.all) && self.all) != (has(self.labelSelector) + && (size(self.labelSelector.matchLabels) > 0 || size(self.labelSelector.matchExpressions) > 0))pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (5)
59-59: Good architectural improvement with contextstore.The replacement of the previous lock+map approach with a centralized
contextstore.Storeprovides better lifecycle management and namespace isolation for sync contexts.
68-68: Namespace parameter addition is consistent.The addition of the
namespaceparameter to the reconcile method properly propagates the namespace context through the reconciliation flow.
369-379: Good defensive programming with label selector error handling.The error handling for label selector conversion properly logs failures without crashing the controller, allowing it to continue with default behavior.
383-387: Efficient informer factory creation based on selector presence.The conditional creation of filtered vs. standard informer factories based on label selector presence optimizes resource usage.
172-172: Fix inconsistent key construction pattern.The key construction should use
export.Nameinstead of the schema name for the second parameter to maintain consistency with the export key pattern.- key := contextstore.NewKey(export.Namespace, export.Name, schema.Name) + key := contextstore.NewKey(export.Namespace, export.Name, schema.Name)Wait, this is actually correct. The pattern is
namespace.exportName.schemaName.
24ab10a to
e8c84d4
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (1)
251-257: Phase toggles from Succeeded to Failed after one minute — logic bugYou set Succeeded, then immediately flip to Failed if age > 1m. Remove the failure block (the 10m TTL below already cleans up stale requests).
- conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) - req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseSucceeded - - if time.Since(req.CreationTimestamp.Time) > time.Minute { - req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseFailed - req.Status.TerminalMessage = conditions.GetMessage(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) - } + conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) + req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseSucceeded
♻️ Duplicate comments (7)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
132-144: CEL: drop redundant null check; presence test is enoughUse has(self.labelSelector) only. This was flagged earlier.
-// +kubebuilder:validation:XValidation:rule="(has(self.all) && self.all) != (has(self.labelSelector) && self.labelSelector != null)",message="either \"all\" or \"labelSelector\" must be set" +// +kubebuilder:validation:XValidation:rule="(has(self.all) && self.all) != has(self.labelSelector)",message="either \"all\" or \"labelSelector\" must be set"kcp/deploy/examples/apiserviceexport-cluster.yaml (1)
11-17: Fix field name: use group (not apiGroup) to match JSON tagExamples must match API: GroupResource uses json:"group".
- permissionClaims: - - apiGroup: "" + permissionClaims: + - group: "" resource: configmaps selector: labelSelector: matchLabels: app: wildwest#!/bin/bash # Ensure no lingering apiGroup fields in examples/CRDs rg -n 'apiGroup:' -g 'kcp/deploy/examples/**' -g 'deploy/crd/**'pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (2)
64-67: Fix: Return proper error when status namespace is empty.Currently
erris nil when reaching this condition, which prevents requeue. Create and return a proper error.if sn.Status.Namespace == "" { - runtime.HandleError(err) - return err // hoping the status is set soon. + e := fmt.Errorf("APIServiceNamespace %q has empty status.namespace", providerNS) + runtime.HandleError(e) + return e // hoping the status is set soon. }
131-135: Fix: Pass correct namespace when deleting consumer object.The code incorrectly passes
providerNSwhen deleting the consumer object. It should passconsumerNS.-logger.Info("Deleting downstream object because it has been deleted upstream", "downStreamNamespace", providerNS, "downstreamName", providerObj.GetName()) -if err := r.deleteConsumerObject(ctx, providerNS, providerObj.GetName()); err != nil { +logger.Info("Deleting downstream object because it has been deleted upstream", "downstreamNamespace", consumerNS, "downstreamName", providerObj.GetName()) +if err := r.deleteConsumerObject(ctx, consumerNS, providerObj.GetName()); err != nil { return err }pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (3)
127-131: Fix incorrect key construction for schema cleanup.The key construction uses the schema name twice, which appears incorrect. It should use the export name and schema name.
-key := contextstore.NewKey(namespace, name, name) +key := contextstore.NewKey(namespace, export.Name, schema.Name)
290-294: Fix context generation tracking - use export.Generation instead of schema.Generation.The sync context should track the export's generation for proper lifecycle management, not the schema's generation.
r.syncStore.Set(key, contextstore.SyncContext{ - Generation: schema.Generation, + Generation: export.Generation, Cancel: cancel, })
143-145: Review scope determination for mixed resource types.The
isClusterScopedflag is set based on the last processed schema. When an export contains both cluster and namespace-scoped resources, this could lead to incorrect behavior for permission claims.Consider either:
- Validating that all resources have consistent scoping before processing
- Determining scope per permission claim rather than using a single flag
+// Validate consistent scoping across all schemas +scopeMap := make(map[bool]bool) for _, res := range export.Spec.Resources { // ... existing schema fetching logic ... processedSchemas[name] = true - isClusterScoped = schema.Spec.Scope == apiextensionsv1.ClusterScoped || schema.Spec.InformerScope == kubebindv1alpha2.ClusterScope + currentScope := schema.Spec.Scope == apiextensionsv1.ClusterScoped || schema.Spec.InformerScope == kubebindv1alpha2.ClusterScope + scopeMap[currentScope] = true + isClusterScoped = currentScope // Keep last for backward compatibility } +if len(scopeMap) > 1 { + logger.Info("Mixed cluster/namespace scoped resources detected in export", "export", export.Name) +}
🧹 Nitpick comments (27)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (4)
277-286: Condition message nit: clarify textChange “SchemaNotFound not found” to a clearer message.
- "SchemaNotFound not found", + "no exported schemas found",
322-335: Use a request-scoped condition type for claim validation (or reuse ExportsReady with a reason)Setting APIServiceExportConditionPermissionClaim on an APIServiceExportRequest mixes resource domains. Prefer ExportsReady with reason "InvalidPermissionClaim" here.
- req, - kubebindv1alpha2.APIServiceExportConditionPermissionClaim, + req, + kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, "InvalidPermissionClaim",
82-131: Avoid duplication: share ExportedSchemas listing logicgetExportedSchemas here duplicates handler.getBackendDynamicResource. Consider extracting a shared helper to reduce drift.
260-263: Use structured logging (key/value), not printf-style formattingFile: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go: lines 260-263 — replace the printf-style logger.Info call with key/value pairs.
- logger.Info("Deleting service binding request %s/%s", req.Namespace, req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time)) + logger.Info("Deleting service binding request", + "namespace", req.Namespace, "name", req.Name, + "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time))backend/http/handler.go (3)
383-399: Deterministic UI: sort schemas before renderingMap iteration order is random; sort for stable UI/tests.
result := make([]UISchema, 0, len(exportedSchemas)) for _, item := range exportedSchemas { if len(item.Spec.Versions) == 0 { logger.Error(fmt.Errorf("no versions found"), "skipping schema", "name", item.Name) continue } result = append(result, UISchema{ Name: item.GetName(), Kind: item.Spec.Names.Kind, Scope: string(item.Spec.Scope), Version: item.Spec.Versions[0].Name, Group: item.Spec.Group, Resource: item.Spec.Names.Plural, SessionID: sessionID, }) } +// Ensure deterministic order: group, resource, version +sort.Slice(result, func(i, j int) bool { + if result[i].Group != result[j].Group { + return result[i].Group < result[j].Group + } + if result[i].Resource != result[j].Resource { + return result[i].Resource < result[j].Resource + } + return result[i].Version < result[j].Version +})Add import:
-import ( +import ( + "sort"
542-545: Error text: not always CRDsList targets schemaSource; make error generic.
- return nil, fmt.Errorf("failed to list crds: %w", err) + return nil, fmt.Errorf("failed to list schemas: %w", err)
547-553: Be tolerant to bad items: skip conversion errors instead of failing entire listOne malformed item shouldn’t 500 the page; log and continue.
for _, item := range items.Items { boundSchema, err := helpers.UnstructuredToBoundSchema(item) - if err != nil { - return nil, err - } + if err != nil { + klog.FromContext(ctx).Error(err, "skipping invalid schema", "name", item.GetName()) + continue + } boundSchemas[boundSchema.ResourceGroupName()] = boundSchema }sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
110-115: Immutability message doesn’t match scopeThe rule freezes the entire PermissionClaims slice. Adjust message accordingly.
-// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="Permission claim selector is immutable" +// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable"
165-167: Stringer: align empty-group rendering with ResourceGroupName() (“core”)For consistency in logs/errors, print resource.core when group is empty.
-func (r GroupResource) String() string { - return fmt.Sprintf("%s.%s", r.Resource, r.Group) -} +func (r GroupResource) String() string { + g := r.Group + if g == "" { + g = "core" + } + return fmt.Sprintf("%s.%s", r.Resource, g) +}kcp/README.md (2)
108-116: The kubeconfig secret name change needs documentation.The secret name changed from
kubeconfig-wvvsbtokubeconfig-c88q6, and there's a new workflow usingapiserviceexport-cluster.yaml. Please add a comment explaining that the secret name is dynamic and users should check the actual name.# Extract secret for binding process. Note that secret name is not the same as output from command above. Check secret -# name by running `kubectl get secret -n kube-bind` +# name by running `kubectl get secret -n kube-bind` +# Note: The secret name (e.g., kubeconfig-c88q6) is dynamically generated and will differ in your environment kubectl get secret kubeconfig-c88q6 -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote.kubeconfig
124-124: Add language specification to fenced code block.The static analysis correctly identifies a missing language specification for the fenced code block.
-``` +```bashtest/e2e/bind/happy-case_test.go (1)
140-177: Consider extracting permission claims construction to a helper function.The permission claims construction logic could be extracted for better readability and reusability.
+func buildPermissionClaims() []kubebindv1alpha2.PermissionClaim { + return []kubebindv1alpha2.PermissionClaim{ + { + GroupResource: kubebindv1alpha2.GroupResource{ + Group: "", + Resource: "configmaps", + }, + Selector: kubebindv1alpha2.Selector{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "configmaps", + }, + }, + }, + }, + { + GroupResource: kubebindv1alpha2.GroupResource{ + Group: "", + Resource: "secrets", + }, + Selector: kubebindv1alpha2.Selector{ + LabelSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "app": "secrets", + }, + }, + }, + }, + } +} if withPermissionClaims { var request kubebindv1alpha2.APIServiceExportRequest err := json.Unmarshal(inv.Stdin, &request) require.NoError(t, err) - request.Spec.PermissionClaims = []kubebindv1alpha2.PermissionClaim{ - { - GroupResource: kubebindv1alpha2.GroupResource{ - Group: "", - Resource: "configmaps", - }, - Selector: kubebindv1alpha2.Selector{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "configmaps", - }, - }, - }, - }, - { - GroupResource: kubebindv1alpha2.GroupResource{ - Group: "", - Resource: "secrets", - }, - Selector: kubebindv1alpha2.Selector{ - LabelSelector: &metav1.LabelSelector{ - MatchLabels: map[string]string{ - "app": "secrets", - }, - }, - }, - }, - } + request.Spec.PermissionClaims = buildPermissionClaims() payload, err := json.Marshal(request) require.NoError(t, err) inv.Stdin = payload }pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (1)
352-453: Consider adding timeout for informer sync.The permission claim controller setup looks good but could benefit from a timeout when waiting for informer sync to prevent indefinite blocking.
go func() { defaultConsumerInf.Start(ctxWithCancel.Done()) + // Add timeout for informer sync + syncCtx, syncCancel := context.WithTimeout(ctxWithCancel, 2*time.Minute) + defer syncCancel() + // Wait for consumer informers to sync - consumerSynced := defaultConsumerInf.WaitForCacheSync(ctxWithCancel.Done()) + consumerSynced := defaultConsumerInf.WaitForCacheSync(syncCtx.Done()) + if syncCtx.Err() != nil { + logger.Error(syncCtx.Err(), "timeout waiting for consumer informer sync") + cancel() + return + } logger.V(2).Info("Synced consumer informers", "consumer", slices.Collect(maps.Keys(consumerSynced)), "key", claimKey) // Start provider informer and wait for sync providerInf.Start(ctxWithCancel) - providerSynced := providerInf.WaitForCacheSync(ctxWithCancel.Done()) + providerSynced := providerInf.WaitForCacheSync(syncCtx.Done()) + if syncCtx.Err() != nil { + logger.Error(syncCtx.Err(), "timeout waiting for provider informer sync") + cancel() + return + } logger.V(2).Info("Synced provider informers", "provider", slices.Collect(maps.Keys(providerSynced)), "key", claimKey) // Start the claimed resources controller claimedCtrl.Start(ctxWithCancel, 1) }()deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (5)
224-228: Clarify semantics: requested vs. decided claims.For APIServiceExportRequest, "PermissionClaims records decisions..." reads like Binding semantics. Here it should describe provider-requested claims. Suggest rewording.
- permissionClaims: - description: |- - PermissionClaims records decisions about permission claims requested by the service provider. - Access is granted per GroupResource. + permissionClaims: + description: |- + PermissionClaims lists permission claims requested by the service provider. + Access is granted per GroupResource.
229-233: Say GroupResource, not GVR.The items describe selecting objects of a GVR, but version isn’t part of the schema (only group/resource). Align wording to GroupResource.
- description: |- - PermissionClaim selects objects of a GVR that a service provider may + description: |- + PermissionClaim selects objects of a GroupResource that a service provider may
273-277: Constrain operator with enum.Add an enum to prevent invalid operators sneaking past validation.
- operator: + operator: description: |- operator represents a key's relationship to a set of values. - Valid operators are In, NotIn, Exists and DoesNotExist. - type: string + Valid operators are In, NotIn, Exists and DoesNotExist. + type: string + enum: + - In + - NotIn + - Exists + - DoesNotExist
305-307: XOR rule is fine; consider guarding empty labelSelector.As written, {} for labelSelector passes presence check. If you want at least one of matchLabels/matchExpressions, tighten the rule.
- rule: (has(self.all) && self.all) != (has(self.labelSelector) - && self.labelSelector != null) + rule: (has(self.all) && self.all) != (has(self.labelSelector) + && self.labelSelector != null + && ((has(self.labelSelector.matchLabels) && size(self.labelSelector.matchLabels) > 0) + || (has(self.labelSelector.matchExpressions) && size(self.labelSelector.matchExpressions) > 0)))
313-315: Selector immutability rule currently freezes the entire list.
rule: self == oldSelfat the array level makes all of permissionClaims immutable (adds/removes/edits), while the message mentions only the selector. Either rename the message, or scope immutability to selector per item by keying the list.- x-kubernetes-validations: - - message: Permission claim selector is immutable - rule: self == oldSelf + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource + x-kubernetes-validations: + - message: selector is immutable for a given {group,resource} + rule: self.all(c, + oldSelf.exists(o, o.group == c.group && o.resource == c.resource && o.selector == c.selector)) && + oldSelf.all(o, + self.exists(c, c.group == o.group && c.resource == o.resource && c.selector == o.selector))deploy/crd/kube-bind.io_apiservicebindings.yaml (2)
236-238: Use GroupResource consistently (not GVR).Descriptions say “GVR” but version isn’t part of the schema. Align to GroupResource to avoid confusion.
- description: |- - PermissionClaim selects objects of a GVR that a service provider may + description: |- + PermissionClaim selects objects of a GroupResource that a service provider may @@ - description: LabelSelector is a label selector that selects - objects of a GVR. + description: LabelSelector is a label selector that selects + objects of a GroupResource.Also applies to: 254-256
318-319: Key the list to prevent duplicates and ease patching.Add list-map semantics so {group,resource} entries are unique and merge-friendly.
type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resourcekcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (5)
220-224: Clarify semantics: requested vs. decided claims.For ExportRequests, this field represents requested claims, not decisions. Reword to avoid implying Binding semantics.
- permissionClaims: - description: |- - PermissionClaims records decisions about permission claims requested by the service provider. - Access is granted per GroupResource. + permissionClaims: + description: |- + PermissionClaims lists permission claims requested by the service provider. + Access is granted per GroupResource.
225-233: Use GroupResource (not GVR).Wording should match the actual fields (group/resource only).
- description: |- - PermissionClaim selects objects of a GVR that a service provider may + description: |- + PermissionClaim selects objects of a GroupResource that a service provider may
269-277: Constrain operator with enum.Same as the CRD: restrict to {In, NotIn, Exists, DoesNotExist}.
- operator: + operator: description: |- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. type: string + enum: [In, NotIn, Exists, DoesNotExist]
301-303: Tighten XOR to avoid empty labelSelector.Optional, if you want at least one selector predicate.
- rule: (has(self.all) && self.all) != (has(self.labelSelector) - && self.labelSelector != null) + rule: (has(self.all) && self.all) != (has(self.labelSelector) + && self.labelSelector != null + && ((has(self.labelSelector.matchLabels) && size(self.labelSelector.matchLabels) > 0) + || (has(self.labelSelector.matchExpressions) && size(self.labelSelector.matchExpressions) > 0)))
309-311: Selector immutability scope vs. message mismatch.
self == oldSelfat array scope freezes the whole list. Either keep full immutability and update the message, or scope immutability per item by keying the list and validating selector only.- x-kubernetes-validations: - - message: Permission claim selector is immutable - rule: self == oldSelf + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: ["group","resource"] + x-kubernetes-validations: + - message: selector is immutable for a given {group,resource} + rule: self.all(c, + oldSelf.exists(o, o.group == c.group && o.resource == c.resource && o.selector == c.selector)) && + oldSelf.all(o, + self.exists(c, c.group == o.group && c.resource == o.resource && c.selector == o.selector))kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (2)
225-233: Wording: say GroupResource.Descriptions mention GVR but version isn’t modeled. Prefer GroupResource.
- description: |- - PermissionClaim selects objects of a GVR that a service provider may + description: |- + PermissionClaim selects objects of a GroupResource that a service provider may @@ - description: LabelSelector is a label selector that selects - objects of a GVR. + description: LabelSelector is a label selector that selects + objects of a GroupResource.Also applies to: 249-256
313-314: Key the list to prevent duplicates and enable merge semantics.Add list-map semantics for {group,resource}.
type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: ["group","resource"]
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (33)
Makefile(1 hunks)backend/controllers/clusterbinding/clusterbinding_reconcile.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(4 hunks)backend/controllers/servicenamespace/servicenamespace_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(4 hunks)backend/http/handler.go(4 hunks)backend/kubernetes/resources/namespace.go(1 hunks)cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go(1 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)kcp/README.md(6 hunks)kcp/deploy/bootstrap.go(1 hunks)kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)kcp/deploy/examples/apiserviceexport-namespaced.yaml(1 hunks)kcp/deploy/examples/cowboy.yaml(1 hunks)kcp/deploy/examples/sheriff.yaml(1 hunks)kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)pkg/indexers/servicebinding.go(2 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(1 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go(2 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(2 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)sdk/apis/kubebind/v1alpha2/boundchema_types.go(4 hunks)test/e2e/bind/happy-case_test.go(5 hunks)
🚧 Files skipped from review as they are similar to previous changes (19)
- kcp/deploy/examples/apiserviceexport-namespaced.yaml
- pkg/konnector/controllers/contextstore/contextstore.go
- backend/controllers/servicenamespace/servicenamespace_reconcile.go
- backend/controllers/clusterbinding/clusterbinding_reconcile.go
- backend/kubernetes/resources/namespace.go
- pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go
- pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go
- kcp/deploy/bootstrap.go
- pkg/indexers/servicebinding.go
- backend/controllers/servicenamespace/servicenamespace_controller.go
- Makefile
- kcp/deploy/examples/cowboy.yaml
- pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
- kcp/deploy/resources/apiexport-kube-bind.io.yaml
- cli/pkg/kubectl/bind-apiservice/plugin/servicebindings.go
- kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml
- pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go
- deploy/crd/kube-bind.io_apiserviceexports.yaml
- sdk/apis/kubebind/v1alpha2/boundchema_types.go
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-12T08:40:15.272Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.272Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gokcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yamlkcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml
📚 Learning: 2025-09-12T09:05:29.743Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.743Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-12T08:55:41.816Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.816Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
🧬 Code graph analysis (7)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
sdk/apis/kubebind/v1alpha2/register.go (1)
Resource(42-44)
test/e2e/bind/happy-case_test.go (3)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)NamespacedScope(63-63)InformerScope(59-59)test/e2e/framework/clients.go (1)
KubeClient(37-41)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
APIServiceExportRequest(46-60)PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (2)
sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
OwnerProvider(185-185)OwnerConsumer(187-187)Owner(181-181)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (3)
pkg/konnector/controllers/contextstore/contextstore.go (1)
New(57-61)pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/indexers/serviceexport.go (2)
ServiceExportByCustomResourceDefinition(26-26)IndexServiceExportByCustomResourceDefinition(30-37)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (6)
sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
Selector(135-144)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(147-163)PermissionClaim(171-178)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportConditionPermissionClaim(42-42)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (6)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(38-44)Key(28-28)NewKey(34-36)SyncContext(51-55)sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
InformerScope(59-59)ClusterScope(62-62)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(171-178)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(48-55)GetterInformerWrapper(288-291)NewDynamicMultiNamespaceInformer(75-100)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(51-167)
🪛 markdownlint-cli2 (0.17.2)
kcp/README.md
124-124: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
🪛 YAMLlint (1.37.1)
kcp/deploy/examples/sheriff.yaml
[error] 6-6: no new line character at the end of file
(new-line-at-end-of-file)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: go-test-e2e
- GitHub Check: verify
- GitHub Check: lint
- GitHub Check: go-test
🔇 Additional comments (23)
backend/http/handler.go (2)
527-556: Typed ExportedSchemas return is a solid improvementSwitching from unstructured to typed BoundSchema map simplifies callers and reduces parsing errors.
385-396: Confirm "first version wins" policybackend/http/handler.go:393 exposes item.Spec.Versions[0].Name; sdk/apis/kubebind/v1alpha1/helpers/serviceexport.go:118 sets spec.Versions[0].Storage = true. Confirm UX expects the first listed version to be exposed; if not, implement explicit version-selection logic and add tests.
kcp/deploy/examples/sheriff.yaml (1)
4-6: Add trailing newline to kcp/deploy/examples/sheriff.yaml; CRD schema already includes spec.intent
- kcp/deploy/examples/sheriff.yaml currently lacks a trailing newline (EOF_NEWLINE: no) — add a newline at EOF.
- The Sheriff schema already declares spec.intent in kcp/deploy/examples/apiresourceschema-sheriffs.yaml (intent property present), so no schema change required.
kcp/README.md (5)
26-29: LGTM! The Dex startup step is clear and well-documented.The addition of Dex startup instructions is appropriate for authentication flow support in the permission claims feature.
83-84: LGTM! The addition of sheriff and cowboy examples enhances testing capabilities.The new APIResourceSchema files provide good examples for testing permission claims with different resource types.
138-144: LGTM! Debug section enhancements are valuable.The additions for debugging claimed objects with ConfigMaps provide good examples for testing permission claims functionality.
94-94: Confirm whether the hardcoded LogicalCluster ID in kcp/README.md is intentional or replace it with a placeholder.rg found two occurrences in kcp/README.md (example cluster URL and ./bin/kubectl-bind export command). Replace "43d7su0lk1bxyaia" with a clear placeholder (e.g., <CLUSTER_ID>) or document it as an intentional example.
53-54: Confirm default value for --consumer-scope in codeI inspected backend/options/options.go and found the
consumer-scopeflag is registered with options.ConsumerScope as the default; the file shows the flag registration and where tests pass a value ("--consumer-scope="+string(informerScope)). The repository did not show a hardcoded literal default of "namespaced" — the flag uses the Options struct's field as its default, so the README's--consumer-scope=namespacedmust be intentionally explicit in docs but is not proven to be the codebase default.
- Action: If you intend "namespaced" to be the implicit default, set that default on Options.ConsumerScope (or document the explicit flag in README). Otherwise, keep the README example but ensure code's Options.ConsumerScope is initialized to the intended default.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (5)
126-128: LGTM! Index registration for ServiceExport by CRD is well-structured.The addition of the
ServiceExportByCustomResourceDefinitionindex enables efficient lookups of exports by CRD name, supporting the permission claims feature.
98-98: LGTM! Context store initialization replaces map-based sync context.The transition from a map-based sync context to the centralized
contextstore.Storeimproves maintainability and thread safety.
191-216: LGTM! CRD enqueueing logic properly handles ServiceExport indexing.The updated
enqueueCRDmethod correctly:
- Uses the CRD name as the lookup key
- Queries exports via the new index
- Handles NotFound cases appropriately
- Enqueues related exports with proper logging
291-311: LGTM! Namespace-aware reconciliation improves multi-tenancy support.The updated
processmethod correctly handles namespace-scoped reconciliation, passing the namespace context through to the reconciler.
38-38: Import path is correct — no action required.pkg/konnector/controllers/contextstore/contextstore.go exists and both serviceexport files import "github.com/kube-bind/kube-bind/pkg/konnector/controllers/contextstore".
test/e2e/bind/happy-case_test.go (4)
61-66: LGTM! Function signature update enables permission claims testing.The addition of the
withPermissionClaimsparameter allows for comprehensive testing of both scenarios.
223-263: LGTM! Comprehensive test for creating claimed resources.The test properly creates ConfigMaps and Secrets with appropriate labels and verifies the permission claims functionality.
265-294: LGTM! Thorough verification of resource synchronization.The test properly verifies that:
- Resources are synced to the provider
- Data integrity is maintained
- Labels are preserved
296-323: LGTM! Proper cleanup verification.The test ensures that deleting resources on the consumer side properly propagates deletions to the provider side.
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (2)
239-266: LGTM! Owner determination logic is well-structured.The
determineOwnerfunction correctly determines ownership based on labels with appropriate fallback logic for unlabeled resources.
202-235: LGTM! Object sanitization for cross-cluster synchronization.The
candidateFromOwnerObjfunction properly sanitizes objects by:
- Removing cluster-specific metadata
- Filtering internal annotations and labels
- Setting appropriate namespace
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (2)
298-350: LGTM! Well-structured permission claims controller management.The
ensureControllersForPermissionClaimsfunction properly:
- Processes each permission claim with generation tracking
- Manages controller lifecycle based on binding generation
- Cleans up controllers for removed claims
- Uses unique keys with "claim" prefix to avoid conflicts
531-534: LGTM! Condition messages updated to reflect BoundSchema terminology.The condition messages have been properly updated from "BoundAPIResourceSchemas" to "BoundSchemas" for consistency.
kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (1)
5-5: Schema bump verified — references updated. kcp/deploy/resources/apiexport-kube-bind.io.yaml references schema v250918-24ab10a.apiservicebindings.kube-bind.io (line 52) and kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml defines that name (line 5).kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (1)
5-5: Schema name bump verified — apiexport updated.kcp/deploy/resources/apiexport-kube-bind.io.yaml:57 references schema v250918-24ab10a.apiserviceexportrequests.kube-bind.io; kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml contains the matching APIResourceSchema name. No other files reference the versioned APIResourceSchema name (docs and CRD reference the base resource name only).
|
Tests are failing as backend does not quite support right rbac setup. Will add separate controller foRBAC, as now itit'sall around the place "leaching" on other controllers. Not nice. |
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
171-178: Fix possible panic when sns is nil during delete handlingWhen sns is nil (NotFound), logging
sns.Status.Namespacewill panic. Guard the log.- if cancel, found := inf.namespaceCancel[name]; found { - logger.V(2).Info("stopping informer", "namespace", sns.Status.Namespace) + if cancel, found := inf.namespaceCancel[name]; found { + nsLog := "" + if sns != nil { + nsLog = sns.Status.Namespace + } + logger.V(2).Info("stopping informer", "namespace", nsLog) delete(inf.namespaceCancel, name) delete(inf.namespaceInformers, name) cancel() }pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
259-269: Bug: updateCRD error is swallowedOn update failure (non-Invalid), the function returns nil, hiding the error.
- if _, err := r.updateCRD(ctx, crd); err != nil && !errors.IsInvalid(err) { - return nil + if _, err := r.updateCRD(ctx, crd); err != nil && !errors.IsInvalid(err) { + return err } else if errors.IsInvalid(err) {pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (1)
120-145: Fix wrong contextstore key when BoundSchema is missing (leaks controllers).name shadows the function arg; key uses schema name twice, dropping export name. This prevents cleanup.
- for _, res := range export.Spec.Resources { - name := res.ResourceGroupName() + for _, res := range export.Spec.Resources { + schemaName := res.ResourceGroupName() // Fetch the APIResourceSchema - schema, err := r.getRemoteBoundSchema(ctx, name) + schema, err := r.getRemoteBoundSchema(ctx, schemaName) @@ - key := contextstore.NewKey(namespace, name, name) + key := contextstore.NewKey(namespace, export.Name, schemaName) deleted := r.syncStore.BulkDeletePrefixed(key) for _, k := range deleted { logger.V(1).Info("Stopping APIServiceExport sync", "key", k.Key(), "reason", "BoundSchema not found") } continue } @@ - processedSchemas[name] = true // This is only schemas names (suffix) + processedSchemas[schemaName] = true // track processed schema names
♻️ Duplicate comments (4)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
91-95: Spec wording + uniqueness guard for permissionClaimsSpec should “request” permissions; “records decisions” belongs to Status. Also enforce unique GR pairs. This was raised earlier; repeating here for the new commit.
- // PermissionClaims records decisions about permission claims requested by the service provider. - // Access is granted per GroupResource. - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable" + // PermissionClaims requests permissions for related resources per GroupResource. + // Controllers record acceptance/decisions elsewhere (e.g., Binding Status). + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable" + // +kubebuilder:validation:XValidation:rule="self.map(c, c.group + \"/\" + c.resource).distinct().size() == self.size()",message="permissionClaims must target unique group/resource pairs" PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (3)
357-359: Don’t log entire objects; log keys only.Reduces noise and avoids dumping full objects to logs.
Apply this diff:
- for _, obj := range objs { - logger.Info("enqueueing provider object", "obj", obj) - + for _, obj := range objs {
223-231: Tombstones handled correctly for consumer deletes. Resolved.Nice fix; avoids panics on cache.DeletedFinalStateUnknown.
274-279: Readiness gated on sn.Status.Namespace. Resolved.Correctly uses Status.Namespace to gate readiness for mapping.
🧹 Nitpick comments (15)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
106-109: Enforce uniqueness and tighten the contract of Status.permissionClaimsStatus should remain consistent and deduplicated. Add a validation rule to prevent duplicate GR pairs.
// PermissionClaims records decisions about permission claims requested by the service provider. // Access is granted per GroupResource. - PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"` + // +kubebuilder:validation:XValidation:rule="self.map(c, c.group + \"/\" + c.resource).distinct().size() == self.size()",message="permissionClaims must target unique group/resource pairs" + PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
109-111: Minor: use “gvr” key for a GroupVersionResource valueNit: the log field name is “gvk” while the value is a GVR.
- logger := klog.FromContext(ctx).WithValues("controller", controllerName, "gvk", inf.gvr) + logger := klog.FromContext(ctx).WithValues("controller", controllerName, "gvr", inf.gvr)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
151-157: Copying permission claims inside the per-schema loop is redundantSet once outside the loop to avoid repeated writes.
- for _, schema := range schemas { - if err := r.referenceBoundSchema(ctx, binding, schema.Name); err != nil { - errs = append(errs, err) - } - - if err := r.referencePermissionClaims(ctx, binding, export); err != nil { - errs = append(errs, err) - } + // reflect permission claims once per export + if err := r.referencePermissionClaims(ctx, binding, export); err != nil { + errs = append(errs, err) + } + for _, schema := range schemas { + if err := r.referenceBoundSchema(ctx, binding, schema.Name); err != nil { + errs = append(errs, err) + } if err := r.ensureCRDsFromBoundSchema(ctx, binding, schema); err != nil { errs = append(errs, err) } }kcp/README.md (1)
124-127: Add language hint to fenced blockMissing language spec trips markdownlint (MD040).
-Create objects: -``` +Create objects: +```bash kubectl apply -f kcp/deploy/examples/cowboy.yaml kubectl apply -f kcp/deploy/examples/sheriff.yaml</blockquote></details> <details> <summary>backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)</summary><blockquote> `91-97`: **Verbs set to “*” is acknowledged and intentional; add a clarifying comment** Given kube-bind’s namespace boundary and bidirectional flows, “*” is a deliberate choice. Add a brief comment to document the rationale for future readers. ```diff - // We need list and watch for informers to be able to start. And create to create initial object. - Verbs: []string{"*"}, + // Intentionally broad: within the consumer-owned provider namespace, bidirectional flows + // (e.g., initial object creation) require full verbs. See design note in PR #304. + Verbs: []string{"*"},
199-206: Helper naming is fine; consider owner references for cleanup (optional)Attaching OwnerReferences (e.g., to the APIServiceNamespace or backing Namespace) would simplify garbage collection of per-export RBAC.
backend/http/handler.go (1)
383-403: Make resources list deterministic and pick storage/served version, not index 0.
- Iterating a map yields random order; this can flake UI/E2E. Sort keys before building result.
- Use storage=true (or first served) instead of Spec.Versions[0].
Apply:
- result := make([]UISchema, 0, len(exportedSchemas)) - for _, item := range exportedSchemas { + // sort keys for deterministic UI ordering + keys := make([]string, 0, len(exportedSchemas)) + for k := range exportedSchemas { + keys = append(keys, k) + } + sort.Strings(keys) + + result := make([]UISchema, 0, len(exportedSchemas)) + for _, k := range keys { + item := exportedSchemas[k] if !strings.EqualFold(h.scope.String(), string(item.Spec.Scope)) && h.scope != kubebindv1alpha2.ClusterScope { continue } if len(item.Spec.Versions) == 0 { logger.Error(fmt.Errorf("no versions found"), "skipping schema", "name", item.Name) continue } + version := "" + for _, v := range item.Spec.Versions { + if v.Storage { + version = v.Name + break + } + } + if version == "" { + for _, v := range item.Spec.Versions { + if v.Served { + version = v.Name + break + } + } + } + if version == "" { + logger.Error(fmt.Errorf("no served versions found"), "skipping schema", "name", item.Name) + continue + } result = append(result, UISchema{ Name: item.GetName(), Kind: item.Spec.Names.Kind, Scope: string(item.Spec.Scope), - Version: item.Spec.Versions[0].Name, + Version: version, Group: item.Spec.Group, // Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it. Resource: item.Spec.Names.Plural, SessionID: sessionID, }) }Add import:
import ( @@ "time" "github.com/gorilla/mux" "github.com/gorilla/securecookie" + "sort"kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml (1)
490-582: Tighten CEL XOR to presence-only check for labelSelector.null checks on objects are redundant; presence alone suffices and is consistent with earlier fixes.
- - message: either "all" or "labelSelector" must be set - rule: (has(self.all) && self.all) != (has(self.labelSelector) - && self.labelSelector != null) + - message: either "all" or "labelSelector" must be set + rule: (has(self.all) && self.all) != has(self.labelSelector)kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (1)
303-391: Match CEL XOR style with exports schema (presence-only).Use the same presence-only XOR expression for clarity and consistency.
- - message: either "all" or "labelSelector" must be set - rule: (has(self.all) && self.all) != (has(self.labelSelector) - && self.labelSelector != null) + - message: either "all" or "labelSelector" must be set + rule: (has(self.all) && self.all) != has(self.labelSelector)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (1)
87-99: Use NewKey for export prefix to avoid format drift.Prefer helper consistently.
- exportKey := contextstore.Key(namespace + "." + name) // Key for the export + exportKey := contextstore.NewKey(namespace, name) // Key for the exportpkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (5)
271-273: Surface non-NotFound errors when fetching APIServiceNamespace.Currently returns silently; log for visibility.
Apply this diff:
- return + runtime.HandleError(fmt.Errorf("failed to get APIServiceNamespace %s/%s: %w", c.providerNamespace, ns, err)) + return
236-236: Fix log field: this is a GVK, not a GVR.Apply this diff:
- logger.V(2).Info("queueing consumer object", "gvr", o.GroupVersionKind().String(), "key", fmt.Sprintf("%s/%s", o.GetNamespace(), o.GetName())) + logger.V(2).Info("queueing consumer object", "gvk", o.GroupVersionKind().String(), "key", fmt.Sprintf("%s/%s", o.GetNamespace(), o.GetName()))
103-114: Select APIServiceNamespace in the current provider namespace; fix NotFound resource.Avoid picking a mapping from a different provider namespace and correct the resource name in the NotFound error.
Apply this diff:
- sns, err := serviceNamespaceInformer.Informer().GetIndexer().ByIndex(indexers.ServiceNamespaceByNamespace, upstreamNamespace) + sns, err := serviceNamespaceInformer.Informer().GetIndexer().ByIndex(indexers.ServiceNamespaceByNamespace, upstreamNamespace) if err != nil { return nil, err } - if len(sns) == 0 { - return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("APIServiceNamespace").GroupResource(), upstreamNamespace) - } - return sns[0].(*kubebindv1alpha2.APIServiceNamespace), nil + for _, obj := range sns { + sn := obj.(*kubebindv1alpha2.APIServiceNamespace) + if sn.Namespace == providerNamespace { + return sn, nil + } + } + return nil, errors.NewNotFound(schema.GroupResource{ + Group: kubebindv1alpha2.SchemeGroupVersion.Group, + Resource: "apiservicenamespaces", + }, upstreamNamespace)
304-309: ByIndex doesn’t return NotFound; simplify error handling.Apply this diff:
- if err != nil { - if !errors.IsNotFound(err) { - runtime.HandleError(err) - } - return - } + if err != nil { + runtime.HandleError(err) + return + }
208-211: Log invalid label selectors instead of silently ignoring.A bad selector on a claim should be observable.
Apply this diff:
- selector, err := metav1.LabelSelectorAsSelector(c.claim.Selector.LabelSelector) - if err != nil { - return false - } + selector, err := metav1.LabelSelectorAsSelector(c.claim.Selector.LabelSelector) + if err != nil { + runtime.HandleError(fmt.Errorf("invalid label selector on claim %q/%q: %w", c.claim.Group, c.claim.Resource, err)) + return false + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (22)
backend/controllers/servicenamespace/servicenamespace_reconcile.go(4 hunks)backend/http/handler.go(4 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)kcp/README.md(6 hunks)kcp/deploy/bootstrap.go(1 hunks)kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)pkg/indexers/servicenamespace.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(4 hunks)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(7 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(3 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go(2 hunks)sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go(7 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
- pkg/konnector/controllers/contextstore/contextstore.go
- kcp/deploy/resources/apiexport-kube-bind.io.yaml
- kcp/deploy/bootstrap.go
- kcp/deploy/examples/apiserviceexport-cluster.yaml
- pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go
- pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go
- deploy/crd/kube-bind.io_apiservicebindings.yaml
- deploy/crd/kube-bind.io_apiserviceexports.yaml
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gokcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlkcp/README.md
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gokcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlkcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gokcp/README.md
🧬 Code graph analysis (12)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
pkg/indexers/servicenamespace.go (5)
sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/serviceexport.go (2)
IndexServiceExportByCustomResourceDefinition(30-37)IndexServiceExportByBoundSchema(40-52)pkg/indexers/serviceexportrequest.go (1)
IndexServiceExportRequestByServiceExport(46-56)pkg/konnector/controllers/cluster/serviceexport/status/status_controller.go (1)
c(269-306)pkg/indexers/servicebinding.go (1)
IndexServiceBindingByKubeconfigSecret(28-34)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(171-178)
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
sdk/client/informers/externalversions/kubebind/v1alpha2/boundschema.go (1)
NewFilteredBoundSchemaInformer(59-91)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (4)
pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/konnector/controllers/contextstore/contextstore.go (1)
New(60-64)pkg/indexers/serviceexport.go (2)
ServiceExportByCustomResourceDefinition(26-26)IndexServiceExportByCustomResourceDefinition(30-37)pkg/konnector/controllers/cluster/servicebinding/servicebinding_controller.go (4)
c(204-222)queue(159-172)c(296-332)c(185-202)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (4)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
APIServiceBinding(64-74)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
BoundSchemaToCRD(131-200)backend/controllers/serviceexport/serviceexport_reconcile.go (1)
r(49-90)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (6)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(41-47)Key(28-28)NewKey(34-39)SyncContext(54-58)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(52-176)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
NewController(57-223)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(171-178)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(306-309)NewDynamicMultiNamespaceInformer(77-104)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (6)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)backend/controllers/clusterbinding/clusterbinding_reconcile.go (1)
r(124-200)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (4)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
ConditionType(48-48)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(171-178)sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (4)
AcceptedNames(216-234)Name(150-202)in(73-75)in(69-71)sdk/apis/kubebind/v1alpha1/helpers/serviceexport.go (1)
ServiceExportToCRD(34-74)
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (3)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
InternalAPI(29-35)
kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml (3)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
Name(171-223)sdk/apis/kubebind/v1alpha1/apiserviceexport_types.go (4)
OpenAPIV3Schema(204-212)Name(150-202)AcceptedNames(216-234)APIServiceExportCRDSpec(82-100)sdk/apis/kubebind/v1alpha1/helpers/serviceexport.go (1)
CRDToServiceExport(77-124)
🪛 markdownlint-cli2 (0.17.2)
kcp/README.md
124-124: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: verify
- GitHub Check: lint
- GitHub Check: go-test-e2e
- GitHub Check: go-test
🔇 Additional comments (6)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
209-212: Confirm semantics: binding.Status.PermissionClaims mirrors export.SpecIf Status is meant to hold “accepted” claims, consider persisting acceptance decisions (not just a copy) or renaming the comment for clarity. If mirroring is intended, this is fine.
pkg/indexers/servicenamespace.go (1)
26-26: Add deprecated alias for string-literal users (verify usages)Add a temporary, deprecated alias so callers that used the old string literal don't break. My ripgrep run returned "No files were searched" — verify usages with the commands below and confirm no remaining literal references before merging.
Location: pkg/indexers/servicenamespace.go:26
Suggested change:
const ( ServiceNamespaceByNamespace = "serviceNamespaceByNamespace" + // Deprecated: use ServiceNamespaceByNamespace. Kept for transition from older string-literal usages. + ServiceNamespaceByNamespaceLegacy = "ServiceNamespaceByNamespace" )Verification commands:
rg -nP '\bServiceNamespaceByNamespace\b' -g '!vendor/**' -C2
git grep -n 'ServiceNamespaceByNamespace' || truebackend/http/handler.go (1)
531-560: Typed ExportedSchemas conversion LGTM.Switch to typed BoundSchema via helpers.UnstructuredToBoundSchema and map-keyed ExportedSchemas looks solid.
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (1)
971-999: Deep-copy implementations for new types look correct.ExportedSchemas (map deep copy), InternalAPI (Names/GVR/Instance), PermissionClaim/Selector (including metav1.LabelSelector) are implemented as expected for k8s-style deepcopy-gen output.
Please confirm these were generated (not hand-edited) and commit the generator command used (e.g., in a Makefile/README) to keep them reproducible.
Also applies to: 1017-1037, 1086-1123
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (2)
299-351: Permission-claim informer wiring LGTM.Good handling of cluster vs namespaced scopes and optional label selectors on both consumer and provider informers.
Confirm claim scopes are uniform per export (as per design). If that assumption ever changes, pass a per-claim scope instead of a single isClusterScoped.
93-97: Incorrect — contextstore already cancels on Delete/BulkDeletePrefixed
- contextstore.Delete(key) invokes the stored Cancel (nil-checked) and BulkDeletePrefixed(prefix) also calls Cancel() for each removed context, so adding explicit Cancel() at the call sites is unnecessary.
- Optional improvement: BulkDeletePrefixed calls ctx.Cancel() without a nil-check — defend against a nil Cancel to avoid a possible panic (pkg/konnector/controllers/contextstore/contextstore.go: BulkDeletePrefixed).
Likely an incorrect or invalid review comment.
839fec7 to
4c2ef60
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
58-66: Fail fast on nil apiServiceExport in constructorValidate inputs in NewController and remove the runtime panic in reconcile.
Add near the top of NewController:
if apiServiceExport == nil { return nil, fmt.Errorf("apiServiceExport must not be nil") }Once added, you can drop the nil check/panic in reconcile as suggested.
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (1)
138-166: Don’t skip BoundSchema creation when versions are empty.
if len(res.Versions) == 0 { continue }will prevent creating required BoundSchemas when the request omits versions (which is allowed). This later causesBoundSchemaNotFound.- if len(res.Versions) == 0 { - continue - }
🧹 Nitpick comments (27)
contrib/kcp/deploy/examples/cowboy.yaml (1)
5-7: Add trailing newline.YAMLlint flags missing newline at EOF.
Apply:
spec: intent: "ride into the sunset" +contrib/kcp/deploy/examples/sheriff.yaml (1)
4-6: Add trailing newline.Fix no-newline-at-EOF.
Apply:
spec: intent: "ride into the sunset" +contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml (1)
570-577: Unify CEL XOR for selector across schema/CRD.Prefer the simpler, consistent rule used elsewhere: exactly one of all or labelSelector.
Apply:
- x-kubernetes-validations: - - message: either "all" or "labelSelector" must be set - rule: (has(self.all) && self.all) != (has(self.labelSelector) - && self.labelSelector != null) + x-kubernetes-validations: + - message: either "all" or "labelSelector" must be set + rule: (has(self.all) && self.all) != has(self.labelSelector)deploy/crd/kube-bind.io_apiservicebindings.yaml (2)
310-399: Make status.permissionClaims a map by GR to enforce uniqueness and stable patchesDefine the list as a map keyed by group/resource. This prevents duplicate GR entries and improves strategic merge semantics.
Apply this diff:
permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: @@ type: object - type: array + type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource
327-333: Nit: fix article/grammar in descriptionUse “a service binding export” instead of “an service binding export”.
- not provided by an service binding export. + not provided by a service binding export.contrib/kcp/README.md (2)
42-55: Specify language for fenced blocks (markdownlint MD040)Add a language to enable syntax highlighting and satisfy linters.
-``` +```bash k ws use :root:kube-bind ./bin/backend \ --multicluster-runtime-provider kcp \ @@ --schema-source apiresourceschemas \ --consumer-scope=namespaced--- `137-146`: **Define or avoid the shorthand 'k'** These commands use “k” but the alias isn’t defined here. Either switch to kubectl (and kcp plugin) or document the alias. </blockquote></details> <details> <summary>contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (2)</summary><blockquote> `220-312`: **Make spec.permissionClaims a map keyed by GR; keep selector immutability** Mirror the map semantics to prevent duplicates and improve patch behavior. ```diff permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: @@ type: object - type: array + type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource x-kubernetes-validations: - message: Permission claim selector is immutable rule: self == oldSelf
238-241: Nit: fix article/grammar in description- not provided by an service binding export. + not provided by a service binding export.contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (1)
303-391: Stabilize PermissionClaims merge semantics; enforce uniqueness
- Recommend making permissionClaims a map‑semantic list keyed by group+resource to avoid duplicate entries and patch churn.
- Keep XOR validation; it’s correct.
Apply:
- permissionClaims: + permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: + x-kubernetes-map-key-fields: + - group + - resource properties: group: default: "" @@ type: array + x-kubernetes-list-type: mapOptionally add a uniqueness rule:
+ x-kubernetes-validations: + - rule: "self.all(x, i, self.filter(y, j, i == j || (x.group == y.group && x.resource == y.resource)).size() == 1)" + message: "permissionClaims must be unique per {group,resource}"pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (2)
151-156: Avoid redundant updates; set PermissionClaims once per exportCalling referencePermissionClaims inside the schema loop repeats the same assignment N times.
Apply:
- // Process each schema - for _, schema := range schemas { + // Copy permission claims once + if err := r.referencePermissionClaims(ctx, binding, export); err != nil { + errs = append(errs, err) + } + // Process each schema + for _, schema := range schemas { - if err := r.referenceBoundSchema(ctx, binding, schema.Name); err != nil { + if err := r.referenceBoundSchema(ctx, binding, schema.Name); err != nil { errs = append(errs, err) } - - if err := r.referencePermissionClaims(ctx, binding, export); err != nil { - errs = append(errs, err) - }
209-213: Prevent status churn; only assign when changedGuard the assignment to avoid unnecessary status patches; sort/dedupe for stability.
Apply:
func (r *reconciler) referencePermissionClaims(ctx context.Context, binding *kubebindv1alpha2.APIServiceBinding, export *kubebindv1alpha2.APIServiceExport) error { - binding.Status.PermissionClaims = export.Spec.PermissionClaims + if !apiequality.Semantic.DeepEqual(binding.Status.PermissionClaims, export.Spec.PermissionClaims) { + binding.Status.PermissionClaims = append([]kubebindv1alpha2.PermissionClaim(nil), export.Spec.PermissionClaims...) + } return nil }Add import outside this hunk:
import ( // ... apiequality "k8s.io/apimachinery/pkg/api/equality" )pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (4)
66-76: Set User‑Agent for consumer client tooSymmetry helps debugging.
Apply:
- consumerClient, err := dynamicclient.NewForConfig(consumerConfig) + consumerConfig = rest.CopyConfig(consumerConfig) + consumerConfig = rest.AddUserAgent(consumerConfig, controllerName) + consumerClient, err := dynamicclient.NewForConfig(consumerConfig)
312-316: Log the enqueued key; drop unused local varYou log “key” but enqueue upstreamKey.
Apply:
- if sn.Namespace == c.providerNamespace { - key := fmt.Sprintf("%s/%s", sn.Name, name) - logger.V(2).Info("queueing Unstructured", "key", key) - c.queue.Add(upstreamKey) - return - } + if sn.Namespace == c.providerNamespace { + logger.V(2).Info("queueing Unstructured", "key", upstreamKey) + c.queue.Add(upstreamKey) + return + }
351-367: Don’t log entire objectsLogging full Unstructured can leak data and bloat logs; log keys instead.
Apply:
- for _, obj := range objs { - logger.Info("enqueueing provider object", "obj", obj) + for _, obj := range objs { key, err := cache.MetaNamespaceKeyFunc(obj) @@ - logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "ServiceNamespaceKey", key) + logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace") c.queue.Add(key) } @@ - for _, obj := range objects { - logger.Info("enqueueing consumer object", "obj", obj) + for _, obj := range objects { key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) @@ - logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "ConsumerObject", key) + logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace") c.queue.Add(key) }Also applies to: 379-396
370-378: Handle Selector.All when listing consumer objectsIf claim.Selector.All, LabelSelector may be nil; use labels.Everything().
Apply:
- selector, err := metav1.LabelSelectorAsSelector(c.claim.Selector.LabelSelector) + var selector labels.Selector + if c.claim.Selector.All { + selector = labels.Everything() + } else { + var err error + selector, err = metav1.LabelSelectorAsSelector(c.claim.Selector.LabelSelector) + if err != nil { + return + } + } - if err != nil { - return // cannot happen, we validated this earlier - }backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
81-98: Per‑export RBAC rule assembly: OK; add brief rationale commentUsing "*" verbs matches the documented design choice (scoped to consumer‑owned provider namespaces). Add a short code comment to capture this rationale for future readers.
150-171: Avoid shadowing; use distinct var name for new RoleMinor readability nit: don’t shadow “role” when creating the object.
Apply:
- if role == nil { - role := &rbacv1.Role{ + if role == nil { + expected := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: sns.Status.Namespace, }, Rules: permissions, } // Create new Role - if err := client.Create(ctx, role); err != nil { + if err := client.Create(ctx, expected); err != nil { return fmt.Errorf("failed to create Role %s: %w", name, err) }backend/http/handler.go (1)
531-560: BoundSchemas conversion error handling.A single bad/unconvertible item aborts the whole list. Consider logging and continuing to avoid blank UIs when one schema is malformed.
test/e2e/bind/happy-case_test.go (3)
268-268: Fix lint: remove unnecessary leading newline.The linter flags an unnecessary leading newline. Remove the blank line preceding this test step.
- { name: "establish permission claims namespace", step: func(t *testing.T) {
304-325: Timeout consistency.
10*time.Minutehere is much larger than other waits (wait.ForeverTestTimeout). Consider aligning to avoid unnecessarily long test runs on failures.
112-113: Prefer constant for default namespace.Use
metav1.NamespaceDefaultinstead of hardcoding"default".deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (2)
224-316: Make permissionClaims list map-keyed to prevent duplicates and enable strategic merge.Define
x-kubernetes-list-type: mapwithx-kubernetes-list-map-keys: ["group","resource"]onpermissionClaims. This prevents duplicate claims for the same G/R and improves patch semantics.Example:
permissionClaims: type: array x-kubernetes-list-type: map x-kubernetes-list-map-keys: - group - resource
314-316: Validation message mismatch.Rule
self == oldSelffreezes the wholepermissionClaimsarray, but the message says “Permission claim selector is immutable.” Clarify the message (e.g., “permissionClaims are immutable”) or narrow the rule to selectors.backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (1)
268-338: Condition type for invalid permission claim.You mark the APIServiceExportRequest with
APIServiceExportConditionPermissionClaim(an Export condition). Prefer using the request’s condition (e.g., keep usingExportsReadywith reasonInvalidPermissionClaim) for consistency.- kubebindv1alpha2.APIServiceExportConditionPermissionClaim, + kubebindv1alpha2.APIServiceExportRequestConditionExportsReady,pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
138-147: Prefer contextual logger.
klog.FromContext(context.Background())loses request context. Consider threading a logger intoenqueueServiceNamespaceor passingctxto preserve correlation.pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (1)
547-560: Condition summary messages.Minor nit: “BoundSchemasNotValid”/message grammar is fine but consider singular/plural consistency if only one schema fails.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (39)
Makefile(1 hunks)backend/controllers/clusterbinding/clusterbinding_reconcile.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(4 hunks)backend/controllers/servicenamespace/servicenamespace_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(4 hunks)backend/http/handler.go(4 hunks)backend/kubernetes/resources/namespace.go(1 hunks)contrib/kcp/README.md(6 hunks)contrib/kcp/deploy/bootstrap.go(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml(1 hunks)contrib/kcp/deploy/examples/cowboy.yaml(1 hunks)contrib/kcp/deploy/examples/sheriff.yaml(1 hunks)contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)pkg/indexers/servicebinding.go(2 hunks)pkg/indexers/servicenamespace.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(4 hunks)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(7 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go(2 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(3 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go(2 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)sdk/apis/kubebind/v1alpha2/boundchema_types.go(4 hunks)sdk/apis/kubebind/v1alpha2/claimable_apis.go(1 hunks)sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go(7 hunks)test/e2e/bind/happy-case_test.go(16 hunks)test/e2e/framework/clients.go(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
- pkg/indexers/servicenamespace.go
- pkg/indexers/servicebinding.go
- backend/controllers/servicenamespace/servicenamespace_controller.go
- pkg/konnector/controllers/contextstore/contextstore.go
- backend/controllers/clusterbinding/clusterbinding_reconcile.go
- sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go
- sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go
- test/e2e/framework/clients.go
- pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go
- Makefile
- sdk/apis/kubebind/v1alpha2/claimable_apis.go
🧰 Additional context used
🧠 Learnings (6)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
contrib/kcp/deploy/examples/apiserviceexport-cluster.yamlbackend/controllers/servicenamespace/servicenamespace_reconcile.gotest/e2e/bind/happy-case_test.godeploy/crd/kube-bind.io_apiserviceexportrequests.yamlsdk/apis/kubebind/v1alpha2/apiserviceexport_types.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlcontrib/kcp/README.mdbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.godeploy/crd/kube-bind.io_apiserviceexports.yamlpkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gocontrib/kcp/deploy/bootstrap.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamldeploy/crd/kube-bind.io_apiservicebindings.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
backend/controllers/servicenamespace/servicenamespace_reconcile.gotest/e2e/bind/happy-case_test.gocontrib/kcp/README.mdcontrib/kcp/deploy/bootstrap.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.godeploy/crd/kube-bind.io_apiservicebindings.yaml
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
sdk/apis/kubebind/v1alpha2/boundchema_types.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
sdk/apis/kubebind/v1alpha2/boundchema_types.gocontrib/kcp/deploy/resources/apiexport-kube-bind.io.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
sdk/apis/kubebind/v1alpha2/boundchema_types.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go
🧬 Code graph analysis (14)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
ClusterScope(62-62)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)
test/e2e/bind/happy-case_test.go (5)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)NamespacedScope(63-63)InformerScope(59-59)test/e2e/framework/clients.go (2)
KubeClient(39-43)BindClient(57-61)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
APIServiceExportRequest(46-60)PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/cluster-scoped/utils.go (2)
ExtractClusterNs(97-104)Prepend(33-35)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespaceList(64-69)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
Conditions(92-92)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (2)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
ConditionType(48-48)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(171-178)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (5)
sdk/apis/third_party/conditions/util/conditions/setter.go (2)
Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
Selector(135-144)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(147-163)PermissionClaim(171-178)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (3)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
InternalAPI(29-35)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
APIServiceBinding(64-74)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
contrib/kcp/deploy/bootstrap.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (4)
pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/konnector/controllers/contextstore/contextstore.go (1)
New(60-64)pkg/indexers/serviceexport.go (2)
ServiceExportByCustomResourceDefinition(26-26)IndexServiceExportByCustomResourceDefinition(30-37)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (6)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(41-47)Key(28-28)NewKey(34-39)SyncContext(54-58)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(52-176)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-146)Resource(148-148)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(171-178)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(306-309)NewDynamicMultiNamespaceInformer(77-104)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (6)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-146)Resource(148-148)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)
🪛 YAMLlint (1.37.1)
contrib/kcp/deploy/examples/sheriff.yaml
[error] 6-6: no new line character at the end of file
(new-line-at-end-of-file)
🪛 GitHub Check: lint
test/e2e/bind/happy-case_test.go
[failure] 268-268:
unnecessary leading newline (whitespace)
🪛 markdownlint-cli2 (0.18.1)
contrib/kcp/README.md
124-124: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: go-test-e2e
🔇 Additional comments (36)
contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml (1)
11-17: Example manifest looks correct; using group:"" is consistent with API.The permissionClaim for core/v1 ConfigMaps with a labelSelector is valid and aligns with JSON tag
group:"".contrib/kcp/deploy/bootstrap.go (1)
237-251: Good hardening: least‑privilege verbs for apiresourceschemas.Switching to get,list,watch resolves the earlier concern about ["*"] on this claim.
deploy/crd/kube-bind.io_apiserviceexports.yaml (1)
573-576: Make CEL XOR expression consistent with APIResourceSchema.Use the same XOR rule everywhere to avoid drift in validation semantics.
Apply:
- - message: either "all" or "labelSelector" must be set - rule: (has(self.all) && self.all) != (has(self.labelSelector) - && self.labelSelector != null) + - message: either "all" or "labelSelector" must be set + rule: (has(self.all) && self.all) != has(self.labelSelector)Check both CRD and APIResourceSchema now use the identical rule:
#!/bin/bash rg -nP 'either "all" or "labelSelector" must be set' -C2 \ contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml \ deploy/crd/kube-bind.io_apiserviceexports.yamlcontrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml (1)
11-17: Use field “group”, not “apiGroup”, in permissionClaims.The API expects
group(defaults to ""), notapiGroup. Example may mislead copy/paste users.Apply:
- - apiGroup: "" + - group: "" resource: configmaps selector: labelSelector: matchLabels: app: wildwestScan repo for leftover
apiGroup:in examples:#!/bin/bash rg -n '^(\s*)apiGroup\s*:' -g 'contrib/**' -C2backend/kubernetes/resources/namespace.go (1)
31-31: Backwards-compat: accept legacy annotation on read; keep writing new key.rg returned no matches for "example-backend.kube-bind.io/identity" in the repo; absence of matches does not prove external/older namespaces don't use the old key — preserve backward compatibility.
File: backend/kubernetes/resources/namespace.go (around lines 31, 55–56)
const ( - IdentityAnnotationKey = "backend.kube-bind.io/identity" + IdentityAnnotationKey = "backend.kube-bind.io/identity" + OldIdentityAnnotationKey = "example-backend.kube-bind.io/identity" ) @@ - if namespace.Annotations[IdentityAnnotationKey] != id { + val := namespace.Annotations[IdentityAnnotationKey] + if val == "" { + val = namespace.Annotations[OldIdentityAnnotationKey] + } + if val != id { return nil, errors.NewAlreadyExists(corev1.Resource("namespace"), namespace.Name) }Optionally write both keys on create to ease rollout.
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (3)
41-43: Condition type addition looks goodThe new PermissionClaim condition constant is fine and consistent with existing patterns.
91-95: Enforce unique group/resource pairs in spec.permissionClaimsRe-raising earlier feedback to prevent duplicate claims for the same GroupResource.
Add a CEL uniqueness constraint:
// PermissionClaims records decisions about permission claims requested by the service provider. // Access is granted per GroupResource. - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable" + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable" + // +kubebuilder:validation:XValidation:rule="self.map(c, c.group + \"/\" + c.resource).distinct().size() == self.size()",message="permissionClaims must target unique group/resource pairs" PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`
81-111: Verify same-scope invariant for PermissionClaimsI see isClusterScoped derived from the processed schemas and passed into the permission-claim controller logic, but I do not find an explicit validation that rejects mixed-scope PermissionClaims nor a test covering mixed-scope rejection. Confirm enforcement exists (admission webhook or reconcile-level check) and add a unit/test that asserts mixed-scope PermissionClaims are rejected if it’s missing.
- Key locations to check: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (isClusterScoped assignment / ensureControllersForPermissionClaims call — ~lines 118, 144–150, 301–360), sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (PermissionClaims / InformerScope), tests: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile_test.go (uses NamespacedScope).
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
103-104: Plumbing looks goodPassing apiServiceExport into the reconciler aligns with the owner-ref requirement.
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
30-33: Type alias and comment are fineExportedSchemas container type is reasonable; comment LGTM.
contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml (2)
7-44: Wide “*” verbs for permissionClaims acknowledged (by design)Using “*” verbs here matches the current security model (scoped provider namespaces, bidirectional flows).
52-63: Schema refs updated — verify they exist in cluster before publishFound APIResourceSchema files for v250918-24ab10a.apiserviceexportrequests.kube-bind.io (contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml) and v250919-5230589.apiserviceexports.kube-bind.io (contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml). No APIResourceSchema found for v250919-e8c84d4.apiservicebindings.kube-bind.io (referenced in contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml lines 52–63); add or confirm that schema is present and served where this APIExport is applied.
pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (2)
54-56: Don’t panic on nil apiServiceExport; handle gracefullyPanicking can crash the controller. Guard and proceed without ownerRefs or return a typed error instead.
Apply this diff:
- if r.apiServiceExport == nil { // Should never happen, but we check to make sure we dont regress in the future. - panic("apiServiceExport is nil") - } + // Proceed even if apiServiceExport is nil; OwnerReferences will be omitted below.
69-71: OwnerReferences: wrap in a nil-safe closureAvoid deref when apiServiceExport is nil; attach owner only when available.
- OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(r.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport")), - }, + OwnerReferences: func() []metav1.OwnerReference { + if r.apiServiceExport == nil { + return nil + } + return []metav1.OwnerReference{ + *metav1.NewControllerRef(r.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport")), + } + }(),contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (2)
5-5: Version bump: ensure downstream assets are alignedThe schema name change looks fine. Please confirm all manifests/tests referencing the old version are updated.
400-401: Storage version flip to v1alpha2: double‑check migration planv1alpha2 is now storage=true. Verify:
- Any older stored objects don’t require special conversion.
- CRD/clients serving v1alpha2 consistently elsewhere.
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (2)
173-207: Rename to referenceBoundSchema: LGTMFunction behavior unchanged; name is clearer.
295-299: Error context: LGTMUsing ref.ResourceGroupName() in messages improves diagnosability.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (4)
80-83: Index service namespaces by namespace: LGTM
127-129: Index by BoundSchema/CRD, not export nameCurrent indexer maps export→export; CRD event lookups won’t resolve.
Apply:
-indexers.AddIfNotPresentOrDie(serviceExportInformer.Informer().GetIndexer(), cache.Indexers{ - indexers.ServiceExportByCustomResourceDefinition: indexers.IndexServiceExportByCustomResourceDefinition, -}) +indexers.AddIfNotPresentOrDie(serviceExportInformer.Informer().GetIndexer(), cache.Indexers{ + indexers.ServiceExportByBoundSchema: indexers.IndexServiceExportByBoundSchema, +})
192-216: Use the BoundSchema index; simplify error handlingByIndex never returns NotFound; use the CRD→exports index.
Apply:
- name, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + name, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) return } - - exports, err := c.serviceExportIndexer.ByIndex(indexers.ServiceExportByCustomResourceDefinition, name) - if err != nil && !errors.IsNotFound(err) { - runtime.HandleError(err) - return - } else if errors.IsNotFound(err) { - return // skip this secret - } + exports, err := c.serviceExportIndexer.ByIndex(indexers.ServiceExportByBoundSchema, name) + if err != nil { + runtime.HandleError(err) + return + } for _, obj := range exports { export := obj.(*kubebindv1alpha2.APIServiceExport) @@ c.queue.Add(key) }
292-306: Namespace-aware get/process: LGTMAlso applies to: 311-326
backend/controllers/servicenamespace/servicenamespace_reconcile.go (3)
49-51: Context logger: LGTM
173-203: Fix RoleBinding messages and add update pathError strings say “Role”; also keep RoleBinding synced like ClusterRoleBinding.
Apply:
- rolebinding, err := c.getPermissionClaimsRoleBinding(ctx, cache, sns.Status.Namespace, name) + rolebinding, err := c.getPermissionClaimsRoleBinding(ctx, cache, sns.Status.Namespace, name) if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("failed to get Role %s: %w", name, err) + return fmt.Errorf("failed to get RoleBinding %s: %w", name, err) } if rolebinding == nil { - rolebinding := &rbacv1.RoleBinding{ + expected := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: sns.Status.Namespace, }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", Namespace: sns.Namespace, Name: kuberesources.ServiceAccountName, }, }, RoleRef: rbacv1.RoleRef{ Kind: "Role", Name: name, APIGroup: "rbac.authorization.k8s.io", }, } - if err := client.Create(ctx, rolebinding); err != nil { - return fmt.Errorf("failed to create Role %s: %w", name, err) + if err := client.Create(ctx, expected); err != nil { + return fmt.Errorf("failed to create RoleBinding %s: %w", name, err) } } else { - logger.Info("Role already exists, update not implemented.", "name", name) + expectedSubjects := []rbacv1.Subject{{ + Kind: "ServiceAccount", + Namespace: sns.Namespace, + Name: kuberesources.ServiceAccountName, + }} + expectedRef := rbacv1.RoleRef{Kind: "Role", Name: name, APIGroup: "rbac.authorization.k8s.io"} + if !reflect.DeepEqual(rolebinding.Subjects, expectedSubjects) || !reflect.DeepEqual(rolebinding.RoleRef, expectedRef) { + rb := rolebinding.DeepCopy() + rb.Subjects = expectedSubjects + rb.RoleRef = expectedRef + if err := client.Update(ctx, rb); err != nil { + return fmt.Errorf("failed to update RoleBinding %s: %w", name, err) + } + } }
251-293: Helper getters/list: LGTMpkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
255-263: Nil deref risk when apiServiceExport is nilOwnerReferences dereferences c.apiServiceExport without a nil check.
Apply:
- _, err := c.createServiceNamespace(context.TODO(), &kubebindv1alpha2.APIServiceNamespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: ns, - Namespace: c.providerNamespace, - OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(c.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport")), - }, - }, - }) + sns := &kubebindv1alpha2.APIServiceNamespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + Namespace: c.providerNamespace, + }, + } + if c.apiServiceExport != nil { + sns.OwnerReferences = []metav1.OwnerReference{ + *metav1.NewControllerRef(c.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport")), + } + } + _, err := c.createServiceNamespace(context.TODO(), sns)test/e2e/bind/happy-case_test.go (1)
231-264: Nice coverage for permission-claims create path.Creating labeled ConfigMap/Secret on consumer and asserting provider sync looks good.
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (1)
82-130: getExportedSchemas: solid approach.Direct client.List with explicit List GVK and label selector avoids cache pitfalls; conversion via helper is clean.
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (3)
971-999: DeepCopy for ExportedSchemas: LGTM.Map entries are deep-copied; return-by-value helper is correct.
1017-1036: DeepCopy for InternalAPI: LGTM.Names and Instance are deep-copied appropriately.
1086-1123: DeepCopy for PermissionClaim/Selector: LGTM.Selector’s nested LabelSelector gets a proper deep copy.
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
192-205: Apply label selectors when provided – good.Conditional use of filtered informer factories per namespace is correct and keeps provider-side watch load down.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (4)
170-197: Controller keying and generation handling: LGTM.Key shape
export.Namespace/export.Name/schema.Nameand generation gating look correct.
299-351: Per-claim controller orchestration is sound.
- Keys are namespaced under
exportwith"claim"suffix.- Generation comparison against
binding.Generationis appropriate.- Cleanup via prefix listing is robust.
353-479: Selector-aware informer setup: LGTM.Four-mode matrix (cluster/namespaced × filtered/unfiltered) is implemented correctly; label selectors applied on both consumer and provider sides.
127-131: Fix contextstore key: export name is missing (regression of earlier comment).The key uses the schema name twice (
namespace, name, name), which won’t match the keys created inensureControllerForSchema(namespace, export.Name, schema.Name). Use the export name for the second segment.- key := contextstore.NewKey(namespace, name, name) + key := contextstore.NewKey(namespace, export.Name, name)
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
test/e2e/bind/happy-case_test.go (1)
307-307: Consider reducing timeout for ConfigMap sync verification.Line 307 uses a 10-minute timeout for ConfigMap synchronization, which seems excessive compared to other operations that use
wait.ForeverTestTimeout. This could make tests unnecessarily slow.- }, 10*time.Minute, time.Millisecond*100, "waiting for configmap to be synced to provider side") + }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for configmap to be synced to provider side")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
test/e2e/bind/happy-case_test.go(16 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
test/e2e/bind/happy-case_test.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
test/e2e/bind/happy-case_test.go
🧬 Code graph analysis (1)
test/e2e/bind/happy-case_test.go (4)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)NamespacedScope(63-63)InformerScope(59-59)test/e2e/framework/clients.go (2)
KubeClient(39-43)BindClient(57-61)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
APIServiceExportRequest(46-60)PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/cluster-scoped/utils.go (2)
ExtractClusterNs(97-104)Prepend(33-35)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: lint
- GitHub Check: go-test-e2e
🔇 Additional comments (11)
test/e2e/bind/happy-case_test.go (11)
21-29: LGTM: Imports added for PermissionClaims functionality.The addition of
encoding/jsonfor marshaling/unmarshaling the APIServiceExportRequest andcorev1for ConfigMaps and Secrets is appropriate for the PermissionClaims feature.
48-58: LGTM: Test cases properly expanded for PermissionClaims coverage.The test matrix correctly covers all combinations of resource scopes (cluster/namespace), informer scopes, and permission claims (enabled/disabled), ensuring comprehensive test coverage.
61-66: LGTM: Function signature enhanced for PermissionClaims testing.The addition of the
withPermissionClaimsboolean parameter allows the same test logic to be used for both traditional binding and permission claims scenarios.
91-94: LGTM: Client initialization for PermissionClaims operations.The initialization of CoreV1 clients for both consumer and provider sides, along with the provider Bind client, is correctly done to support ConfigMap and Secret operations in the PermissionClaims functionality.
112-112: LGTM: Namespace variable naming clarification.The renaming from
downstream/upstreamtoconsumerNS/providerNSimproves code clarity and aligns with the kube-bind terminology.
141-178: LGTM: PermissionClaims configuration correctly implemented.The conditional addition of PermissionClaims to the APIServiceExportRequest is well-structured:
- ConfigMaps and Secrets are properly configured with matching label selectors
- The JSON marshaling/unmarshaling is handled correctly
- The claims follow the expected GVR format with appropriate selectors
223-265: LGTM: Consumer-side resource creation properly implemented.The test step correctly creates ConfigMap and Secret resources on the consumer side with:
- Appropriate labels matching the PermissionClaims selectors
- Valid test data for verification
- Proper error handling
266-294: LGTM: Namespace establishment logic is sound.The conditional namespace establishment for cluster-scoped resources with cluster-scoped informers correctly:
- Only runs when both conditions are met
- Waits for APIServiceNamespace creation
- Updates the provider namespace for subsequent operations
- Includes proper error handling
This aligns with the retrieved learning that permission claims must have consistent scopes.
295-325: LGTM: Provider-side sync verification implemented correctly.The verification step properly:
- Waits for resources to be synced to the provider namespace
- Validates the data integrity (exact content matching)
- Uses appropriate timeouts and retry intervals
326-354: LGTM: Deletion propagation test correctly implemented.The test step properly validates the bidirectional sync by:
- Deleting resources from the consumer side
- Verifying they are removed from the provider side
- Using appropriate error checking for NotFound conditions
200-200: Consistent namespace usage throughout test operations.All resource operations now correctly use
consumerNS/providerNSvariables instead of hardcoded namespace names, ensuring consistent behavior across different test scenarios and improving maintainability.Also applies to: 216-216, 360-360, 369-369, 384-384, 391-391, 404-404, 431-431, 438-438, 450-450, 468-468, 475-475, 488-488, 513-513, 522-522
a7635b9 to
1b9faa9
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (7)
backend/http/handler.go (2)
383-415: Deterministic UI ordering: iterate map keys in sorted order.Map iteration is random; sort to keep UI stable and tests deterministic.
- result := make([]UISchema, 0, len(exportedSchemas)) - for _, item := range exportedSchemas { + result := make([]UISchema, 0, len(exportedSchemas)) + keys := make([]string, 0, len(exportedSchemas)) + for k := range exportedSchemas { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + item := exportedSchemas[k] if !strings.EqualFold(h.scope.String(), string(item.Spec.Scope)) && h.scope != kubebindv1alpha2.ClusterScope { continue }
558-561: Fix error message to reflect actual GVK.Message says “crds” regardless of requested GVK.
- return nil, fmt.Errorf("failed to list crds: %w", err) + return nil, fmt.Errorf("failed to list %s: %w", gvk.String(), err)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (2)
87-89: TODO in API reference: resolve or remove.Either keep
apiServiceExportas required (document) or refactor away; avoid lingering TODOs.
302-310: Simplify indexer error handling.
ByIndexwon’t return NotFound; drop the special-case branch.- sns, err := c.serviceNamespaceInformer.Informer().GetIndexer().ByIndex(indexers.ServiceNamespaceByNamespace, ns) - if err != nil { - if !errors.IsNotFound(err) { - runtime.HandleError(err) - } - return - } + sns, err := c.serviceNamespaceInformer.Informer().GetIndexer().ByIndex(indexers.ServiceNamespaceByNamespace, ns) + if err != nil { + runtime.HandleError(err) + return + }backend/controllers/servicenamespace/servicenamespace_reconcile.go (3)
96-101: Correct log: ClusterRole, not Role.- if err != nil && !errors.IsNotFound(err) { - return fmt.Errorf("failed to get Role %s: %w", name, err) + if err != nil && !errors.IsNotFound(err) { + return fmt.Errorf("failed to get ClusterRole %s: %w", name, err)
112-117: Avoid unnecessary updates.Only update when rules differ.
- } else { - role.Rules = permissions - if err := client.Update(ctx, role); err != nil { + } else if !reflect.DeepEqual(role.Rules, permissions) { + role = role.DeepCopy() + role.Rules = permissions + if err := client.Update(ctx, role); err != nil { return fmt.Errorf("failed to update ClusterRole %s: %w", name, err) } }
189-211: Shadowing var; prefer ‘expected’ for clarity.Use a distinct var to avoid confusion.
- if rolebinding == nil { - rolebinding := &rbacv1.RoleBinding{ + if rolebinding == nil { + expected := &rbacv1.RoleBinding{ ... - if err := client.Create(ctx, rolebinding); err != nil { + if err := client.Create(ctx, expected); err != nil { return fmt.Errorf("failed to create RoleBinding %s: %w", name, err) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (6)
backend/controllers/serviceexport/serviceexport_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(3 hunks)backend/http/handler.go(4 hunks)pkg/indexers/serviceexport.go(2 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(7 hunks)
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-22T13:32:29.471Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.471Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
backend/controllers/serviceexport/serviceexport_controller.gopkg/indexers/serviceexport.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
📚 Learning: 2025-09-22T13:20:49.933Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.933Z
Learning: In kube-bind, there are two different ResourceGroupName() methods: BoundSchema.ResourceGroupName() for CRDs (always non-empty groups) uses simple fmt.Sprintf formatting, while APIServiceExportRequestResource.ResourceGroupName() for export requests handles empty groups by converting to "core". BoundSchema is exclusively for CRDs which cannot have empty API groups per Kubernetes validation.
Applied to files:
pkg/indexers/serviceexport.gobackend/http/handler.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
backend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
backend/controllers/servicenamespace/servicenamespace_reconcile.go
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gobackend/http/handler.go
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go
📚 Learning: 2025-09-22T13:20:49.933Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.933Z
Learning: In kube-bind, BoundSchema.ResourceGroupName() is only used for CRDs (Custom Resource Definitions), and CRDs must always have non-empty API groups. Therefore, handling empty groups (core API group "") is not necessary in this context, unlike general Kubernetes GroupResource handling.
Applied to files:
backend/http/handler.go
🧬 Code graph analysis (6)
backend/controllers/serviceexport/serviceexport_controller.go (1)
pkg/indexers/serviceexport.go (1)
IndexServiceExportByBoundSchemaControllerRuntime(40-51)
pkg/indexers/serviceexport.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
ClusterScope(62-62)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (5)
pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/indexers/servicenamespace.go (2)
ServiceNamespaceByNamespace(26-26)IndexServiceNamespaceByNamespace(29-36)pkg/konnector/controllers/contextstore/contextstore.go (1)
New(60-64)pkg/indexers/serviceexport.go (2)
ServiceExportByBoundSchema(27-27)IndexServiceExportByBoundSchema(54-65)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (6)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-146)Resource(148-148)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
🔇 Additional comments (13)
backend/http/handler.go (1)
385-386: Confirm scope filter semantics.Condition includes all resources when handler scope is Cluster; verify this is intended for the UI.
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (4)
223-236: Good: tombstone-safe and prefilter by claim.
249-270: OwnerRef on APIServiceNamespace assumes non-nil export.Per lifecycle guarantee this is fine; just confirming the controller never runs without
apiServiceExport.
350-367: Avoid logging entire objects. Log keys instead.Dumping full objects is noisy and may leak data.
- logger.Info("enqueueing service namespace", "upstreamNamespace", sn.Status.Namespace) + logger.Info("enqueueing service namespace", "upstreamNamespace", sn.Status.Namespace) objs, err := c.providerDynamicInformer.List(sn.Status.Namespace) if err != nil { runtime.HandleError(err) return } for _, obj := range objs { - logger.Info("enqueueing provider object", "obj", obj) - key, err := cache.MetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) continue } logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "ServiceNamespaceKey", key) c.queue.Add(key) }
379-395: Avoid logging entire consumer objects.Log keys only.
- for _, obj := range objects { - logger.Info("enqueueing consumer object", "obj", obj) - - key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + for _, obj := range objects { + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) return } _, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { runtime.HandleError(err) return } key = fmt.Sprintf("%s/%s", sn.Status.Namespace, name) - logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "ConsumerObject", key) + logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "source", "consumer") c.queue.Add(key) }backend/controllers/servicenamespace/servicenamespace_reconcile.go (1)
89-95: RBAC “*” verbs: acknowledged design.Using “*” is intentional in kube-bind (scoped to consumer-owned namespaces). No change requested.
backend/controllers/serviceexport/serviceexport_controller.go (1)
61-63: LGTM: switched to controller-runtime compatible indexer.pkg/indexers/serviceexport.go (2)
39-51: LGTM: controller-runtime indexer variant.
53-65: LGTM: wrapper for cache.Indexer registration.pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (4)
80-83: LGTM: add ServiceNamespaceByNamespace index.
127-129: LGTM: register BoundSchema index for exports.
193-215: LGTM: use BoundSchema-derived key for CRD->export mapping.
290-311: LGTM: namespaced lister/get and reconcile signature adjustments.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (5)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (5)
66-76: Add User-Agent for consumer client too (observability + traceability).Provider client sets UA; do the same for consumer for better auditability/debugging.
Apply this diff:
providerClient, err := dynamicclient.NewForConfig(providerConfig) if err != nil { return nil, err } - consumerClient, err := dynamicclient.NewForConfig(consumerConfig) + consumerConfig = rest.CopyConfig(consumerConfig) + consumerConfig = rest.AddUserAgent(consumerConfig, controllerName) + consumerClient, err := dynamicclient.NewForConfig(consumerConfig) if err != nil { return nil, err }
109-113: Wrong resource in NotFound error; use plural, lowercase resource.Pass the correct resource name to avoid confusing NotFound diagnostics.
Apply this diff:
- if len(sns) == 0 { - return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("APIServiceNamespace").GroupResource(), upstreamNamespace) - } + if len(sns) == 0 { + return nil, errors.NewNotFound(kubebindv1alpha2.SchemeGroupVersion.WithResource("apiservicenamespaces").GroupResource(), upstreamNamespace) + }
117-123: Avoid panic: check type from providerDynamicInformer.Get before cast.Getter returns runtime.Object; unguarded cast can panic.
Apply this diff:
- getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { - obj, err := providerDynamicInformer.Get(ns, name) - if err != nil { - return nil, err - } - return obj.(*unstructured.Unstructured), nil - }, + getProviderObject: func(ns, name string) (*unstructured.Unstructured, error) { + obj, err := providerDynamicInformer.Get(ns, name) + if err != nil { + return nil, err + } + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("unexpected type %T from providerDynamicInformer.Get", obj) + } + return u, nil + },
304-309: Simplify error handling; indexer won’t return NotFound like the API server.Handle any error from ByIndex uniformly.
Apply this diff:
- if err != nil { - if !errors.IsNotFound(err) { - runtime.HandleError(err) - } - return - } + if err != nil { + runtime.HandleError(err) + return + }
236-236: Nit: log label should be “gvk”, not “gvr”.You’re logging a GroupVersionKind string.
Apply this diff:
- logger.V(2).Info("queueing consumer object", "gvr", o.GroupVersionKind().String(), "key", fmt.Sprintf("%s/%s", o.GetNamespace(), o.GetName())) + logger.V(2).Info("queueing consumer object", "gvk", o.GroupVersionKind().String(), "key", fmt.Sprintf("%s/%s", o.GetNamespace(), o.GetName()))
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-22T13:32:29.471Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.471Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
🧬 Code graph analysis (1)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (5)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: lint
- GitHub Check: verify
- GitHub Check: go-test
- GitHub Check: go-test-e2e
🔇 Additional comments (2)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (2)
223-233: Nice: proper tombstone handling avoids panics on delete events.Tombstone unwrap + guarded type assert is correct.
368-381: Good: Selector.All path handled via labels.Everything().This fixes the nil LabelSelector bug from earlier iterations.
ed50846 to
902fcda
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (3)
66-76: Add user-agent/copy for consumer client config too.Mirror provider setup for the consumer to ensure distinct UA and avoid mutating shared configs.
providerConfig = rest.CopyConfig(providerConfig) providerConfig = rest.AddUserAgent(providerConfig, controllerName) +consumerConfig = rest.CopyConfig(consumerConfig) +consumerConfig = rest.AddUserAgent(consumerConfig, controllerName) + providerClient, err := dynamicclient.NewForConfig(providerConfig) if err != nil { return nil, err } consumerClient, err := dynamicclient.NewForConfig(consumerConfig)
117-123: Harden type assertion on provider object.Avoid panic if the informer ever returns a non‑unstructured object.
-obj, err := providerDynamicInformer.Get(ns, name) +obj, err := providerDynamicInformer.Get(ns, name) if err != nil { return nil, err } -return obj.(*unstructured.Unstructured), nil +u, ok := obj.(*unstructured.Unstructured) +if !ok { + return nil, fmt.Errorf("unexpected type %T from providerDynamicInformer.Get", obj) +} +return u, nil
304-312: Simplify indexer error handling; ByIndex doesn’t return NotFound.Handle errors generically; rely on len(sns)==0 for the empty case.
- if err != nil { - if !errors.IsNotFound(err) { - runtime.HandleError(err) - } - return - } + if err != nil { + runtime.HandleError(err) + return + }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-22T13:32:29.471Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.471Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
🧬 Code graph analysis (1)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (4)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-146)Resource(148-148)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: verify
- GitHub Check: lint
- GitHub Check: go-test
- GitHub Check: go-test-e2e
🔇 Additional comments (5)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (5)
201-221: Selector matching logic looks correct.
352-366: Good: log keys, not full objects.Prevents noisy logs and accidental data leakage.
388-405: Good: per‑item error handling with continue.Avoids aborting the loop on individual key build failures.
418-429: Ensure provider dynamic informer is started and synced.If not started elsewhere, start and wait for sync here to avoid missing events.
c.serviceNamespaceInformer.Informer().AddDynamicEventHandler(ctx, controllerName, cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { c.enqueueServiceNamespace(logger, obj) }, @@ }) +// Ensure provider dynamic informer is running and caches are synced +c.providerDynamicInformer.Start(ctx) +c.providerDynamicInformer.WaitForCacheSync(ctx.Done()) + for i := 0; i < numThreads; i++ { go wait.UntilWithContext(ctx, c.startWorker, time.Second) }Please confirm whether the caller already starts/syncs this informer; if so, skip this to avoid double starts.
259-261: OwnerReference without nil‑check is fine per controller lifecycle.Accepting as-is given the controller doesn’t run without apiServiceExport.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
backend/controllers/serviceexportrequest/serviceexportrequest_controller.go (1)
180-185: Add RBAC for BoundSchema CRUD/read.This controller creates and reads BoundSchema resources but lacks RBAC for them, which will 403 at runtime.
Apply this diff:
//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiserviceexports,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=kubebind.k8s.io,resources=apiresourceschemas,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=boundschemas,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=kubebind.k8s.io,resources=boundschemas/status,verbs=get;update;patchbackend/http/handler.go (1)
254-259: Fix OAuth state decoding: use URL base64 to match encoding.Encoded with base64.URLEncoding, decoded with StdEncoding → breaks for '-'/'_'. Use URLEncoding.
Apply this diff:
- decoded, err := base64.StdEncoding.DecodeString(state) + decoded, err := base64.URLEncoding.DecodeString(state)
🧹 Nitpick comments (10)
backend/http/handler.go (1)
383-417: Sort schemas for deterministic UI.Map iteration is non-deterministic. Sort results before rendering.
Apply this diff:
for _, item := range exportedSchemas { // ... result = append(result, UISchema{ Name: item.GetName(), Kind: item.Spec.Names.Kind, Scope: string(item.Spec.Scope), Version: ver, Group: item.Spec.Group, // Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it. Resource: item.Spec.Names.Plural, SessionID: sessionID, }) } + // ensure deterministic order in UI + sort.Slice(result, func(i, j int) bool { + if result[i].Group == result[j].Group { + return result[i].Resource < result[j].Resource + } + return result[i].Group < result[j].Group + }) + bs := bytes.Buffer{}Also add the import:
import "sort"contrib/kcp/README.md (2)
53-55: Fix --schema-source to Kind.Version.Group expected by code.Handler/reconciler expect "Kind.Version.Group". Using just "apiresourceschemas" will fail.
Apply this diff:
- --schema-source apiresourceschemas \ + --schema-source APIResourceSchema.v1alpha1.apis.kcp.io \Note: If your kcp exposes APIResourceSchema as v1, use APIResourceSchema.v1.apis.kcp.io.
26-30: Add language hint to fenced code block.Satisfy MD040 and improve formatting.
Apply this diff:
-```bash +```bash ./dex/bin/dex serve ./hack/dex-config-dev.yaml</blockquote></details> <details> <summary>backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (4)</summary><blockquote> `132-169`: **Use direct map lookup instead of O(N*M) scan.** exportedSchemas is a map keyed by ResourceGroupName; use it to avoid nested loops. Apply this diff: ```diff - // Ensure all bound schemas exist - for _, res := range req.Spec.Resources { - if len(res.Versions) == 0 { - continue - } - - for _, boundSchema := range exportedSchemas { - if boundSchema.Spec.Group == res.Group && boundSchema.Spec.Names.Plural == res.Resource { - boundSchema.Name = res.ResourceGroupName() - boundSchema.Namespace = req.Namespace - boundSchema.Spec.InformerScope = r.informerScope - boundSchema.ResourceVersion = "" - - obj, err := r.getBoundSchema(ctx, cl, boundSchema.Namespace, boundSchema.Name) - if err != nil && !apierrors.IsNotFound(err) && !strings.Contains(err.Error(), "no matches for kind") { - return err - } - - // TODO(mjudeikis): https://github.com/kube-bind/kube-bind/issues/297 - if obj != nil { - continue - } - - if err := r.createBoundSchema(ctx, cl, boundSchema); err != nil { - return err - } - } - } - } + // Ensure all bound schemas exist + for _, res := range req.Spec.Resources { + if len(res.Versions) == 0 { + continue + } + boundSchema, ok := exportedSchemas[res.ResourceGroupName()] + if !ok { + // not exported by backend; validate() will surface a condition + continue + } + boundSchema.Name = res.ResourceGroupName() + boundSchema.Namespace = req.Namespace + boundSchema.Spec.InformerScope = r.informerScope + boundSchema.ResourceVersion = "" + + obj, err := r.getBoundSchema(ctx, cl, boundSchema.Namespace, boundSchema.Name) + if err != nil && !apierrors.IsNotFound(err) && !strings.Contains(err.Error(), "no matches for kind") { + return err + } + // TODO(mjudeikis): https://github.com/kube-bind/kube-bind/issues/297 + if obj != nil { + continue + } + if err := r.createBoundSchema(ctx, cl, boundSchema); err != nil { + return err + } + }
260-263: Fix structured log usage (avoid printf in message).klog ignores %s in msg. Use key/value pairs for namespace/name.
Apply this diff:
- logger.Info("Deleting service binding request %s/%s", req.Namespace, req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time)) + logger.Info("Deleting service binding request", "namespace", req.Namespace, "name", req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time))
277-286: Tweak condition message."SchemaNotFound not found" reads odd. Use a clear message.
Apply this diff:
- "SchemaNotFound not found", + "no exported schemas found",
322-336: Validate permission-claim scopes are uniform.Per project semantics, permission claims must share the same scope. Enforce it.
Apply this diff:
- for _, claim := range req.Spec.PermissionClaims { - if !isClaimableAPI(claim) { - conditions.MarkFalse( - req, - kubebindv1alpha2.APIServiceExportConditionPermissionClaim, - "InvalidPermissionClaim", - conditionsapi.ConditionSeverityError, - "Resource %s is not a valid claimable API", - claim.GroupResource.String(), - ) - return fmt.Errorf("resource %s is not a valid claimable API", claim.GroupResource.String()) - } - } + var pcScope apiextensionsv1.ResourceScope + for _, claim := range req.Spec.PermissionClaims { + if !isClaimableAPI(claim) { + conditions.MarkFalse( + req, + kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, + "InvalidPermissionClaim", + conditionsapi.ConditionSeverityError, + "Resource %s is not a valid claimable API", + claim.GroupResource.String(), + ) + return fmt.Errorf("resource %s is not a valid claimable API", claim.GroupResource.String()) + } + // enforce uniform scope across claims + for _, api := range kubebindv1alpha2.ClaimableAPIs { + if claim.Group == api.GroupVersionResource.Group && claim.Resource == api.Names.Plural { + if pcScope == "" { + pcScope = api.ResourceScope + } else if pcScope != api.ResourceScope { + conditions.MarkFalse( + req, + kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, + "DifferentPermissionClaimScopes", + conditionsapi.ConditionSeverityError, + "permission claims must have the same scope; found %v and %v", + pcScope, api.ResourceScope, + ) + return fmt.Errorf("permission claims with different scopes are not allowed") + } + break + } + } + }pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (2)
281-286: Return created=false on AlreadyExists; avoid false-positive notificationsIf Create returns AlreadyExists, we shouldn't report "created" nor notify downstream.
Apply this diff:
- _, err = c.createServiceNamespace(ctx, &kubebindv1alpha2.APIServiceNamespace{ + _, err = c.createServiceNamespace(ctx, &kubebindv1alpha2.APIServiceNamespace{ ObjectMeta: metav1.ObjectMeta{ Name: ns, Namespace: c.providerNamespace, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(c.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport")), }, }, }) - if err != nil && !errors.IsAlreadyExists(err) { - return false, fmt.Errorf("failed to create APIServiceNamespace %q: %w", ns, err) - } - - logger.Info("APIServiceNamespace created successfully") - return true, nil + if errors.IsAlreadyExists(err) { + logger.V(2).Info("APIServiceNamespace already exists") + return false, nil + } + if err != nil { + return false, fmt.Errorf("failed to create APIServiceNamespace %q: %w", ns, err) + } + logger.Info("APIServiceNamespace created successfully") + return true, nil
139-147: Tombstone handling is unnecessary for Add-only handlerThis method is only called from an AddFunc; the tombstone branch will never trigger. Safe to drop for clarity.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (1)
60-60: Nit: clarify commentMinor copy edit for clarity.
Apply this diff:
- syncStore contextstore.Store // by APIServiceExport name. This includes same ctx for resourc claims and crds. + syncStore contextstore.Store // keyed by APIServiceExport. Shares lifecycle for resource claims and CRDs.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
Makefile(4 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(6 hunks)backend/http/handler.go(4 hunks)contrib/kcp/README.md(6 hunks)contrib/kcp/deploy/bootstrap.go(1 hunks)contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml(2 hunks)go.mod(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go(1 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)
✅ Files skipped from review due to trivial changes (2)
- go.mod
- pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- Makefile
- contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml
- contrib/kcp/deploy/bootstrap.go
🧰 Additional context used
🧠 Learnings (9)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/http/handler.go
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gocontrib/kcp/README.md
📚 Learning: 2025-09-22T13:20:49.933Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.933Z
Learning: In kube-bind, there are two different ResourceGroupName() methods: BoundSchema.ResourceGroupName() for CRDs (always non-empty groups) uses simple fmt.Sprintf formatting, while APIServiceExportRequestResource.ResourceGroupName() for export requests handles empty groups by converting to "core". BoundSchema is exclusively for CRDs which cannot have empty API groups per Kubernetes validation.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/http/handler.go
📚 Learning: 2025-09-22T13:20:49.933Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.933Z
Learning: In kube-bind, BoundSchema.ResourceGroupName() is only used for CRDs (Custom Resource Definitions), and CRDs must always have non-empty API groups. Therefore, handling empty groups (core API group "") is not necessary in this context, unlike general Kubernetes GroupResource handling.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/http/handler.go
📚 Learning: 2025-09-22T13:32:29.471Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.471Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.gocontrib/kcp/README.md
🧬 Code graph analysis (6)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (5)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
BoundSchema(41-47)ExportedSchemas(32-32)InformerScope(59-59)sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
Selector(135-144)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(147-163)PermissionClaim(171-178)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (5)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(52-180)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(171-178)Selector(135-144)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)
backend/controllers/serviceexportrequest/serviceexportrequest_controller.go (1)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
BoundSchema(41-47)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (8)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(41-47)Key(28-28)NewKey(34-39)SyncContext(54-58)sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
InformerScope(59-59)ClusterScope(62-62)BoundSchema(41-47)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(52-180)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-146)Resource(148-148)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(171-178)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(306-309)NewDynamicMultiNamespaceInformer(77-104)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (7)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(171-178)GroupResource(147-163)Selector(135-144)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)
🪛 markdownlint-cli2 (0.18.1)
contrib/kcp/README.md
123-123: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: go-test-e2e
- GitHub Check: go-test
- GitHub Check: lint
🔇 Additional comments (6)
backend/http/handler.go (1)
543-572: Consistent GVK parsing and error messaging look good.Client-side list via GVK and label selector is appropriate; conversion to BoundSchema keyed by ResourceGroupName is sound.
If README instructs using a single token (e.g., "apiresourceschemas") for --schema-source, it will fail SplitN(..., ".", 3). Ensure README passes Kind.Version.Group (see README comment).
contrib/kcp/README.md (1)
82-86: Updated paths LGTM.Examples now reference contrib/kcp/deploy paths correctly.
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (1)
82-130: Direct client list + label selector approach LGTM.Avoiding cache for dynamic unstructured list is correct given informer constraints.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (2)
482-487: Good start ordering: Namespaces before claimed resourcesStarting the namespaces controller before claimed resources avoids deadlock on provider informer sync.
128-132: Fix incorrect key for BoundSchema cleanup prefixThe export name is wrong; current code builds prefix with schema name twice, preventing proper cleanup.
Apply this diff:
- key := contextstore.NewKey(namespace, name, name) + key := contextstore.NewKey(namespace, export.Name, name)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
380-402: Scope consumer list to mapped namespace and avoid logging full objectsCluster-wide list can enqueue unrelated objects; logging entire objects is noisy and risky. Also, don't abort loop on per-item errors.
Apply this diff:
- objects, err := c.consumerDynamicLister.List(sel) + objects, err := c.consumerDynamicLister.ByNamespace(sn.Name).List(sel) if err != nil { runtime.HandleError(err) return } for _, obj := range objects { - logger.Info("enqueueing consumer object", "obj", obj) - key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) - continue + continue } _, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { runtime.HandleError(err) - continue + continue } key = fmt.Sprintf("%s/%s", sn.Status.Namespace, name) - logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "ConsumerObject", key) + logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "consumerName", name) c.queue.Add(key) }
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
171-176: Fix potential nil dereference when sns is nil.If the object disappeared (sns == nil), logging
sns.Status.Namespacewill panic.@@ - inf.lock.Lock() - defer inf.lock.Unlock() - if cancel, found := inf.namespaceCancel[name]; found { - logger.V(2).Info("stopping informer", "namespace", sns.Status.Namespace) + inf.lock.Lock() + defer inf.lock.Unlock() + if cancel, found := inf.namespaceCancel[name]; found { + nsForLog := "" + if sns != nil { + nsForLog = sns.Status.Namespace + } + logger.V(2).Info("stopping informer", "namespace", nsForLog) delete(inf.namespaceCancel, name) delete(inf.namespaceInformers, name) cancel() }
🧹 Nitpick comments (29)
contrib/kcp/README.md (7)
31-33: Fix step numbering and add explicit kcp start command.Numbering jumps (1 → 2 → 4) and “Start kcp” lacks a command. Keep numbering sequential and show how to start kcp to avoid confusion.
Apply:
-1. Start kcp -2. Bootstrap kcp: +2. Start kcp: +3. Bootstrap kcp:Optionally add the actual start command under “Start kcp” (example, adjust to your build/output):
+```bash +./bin/kcp start --root-directory .kcp +```Also applies to: 38-38
39-39: Add language to fenced code block (markdownlint MD040).Specify bash for the backend run block.
-``` +```bash
54-55: Clarify consumer scope vs. permission-claim scope.Add a short note that all permission claims must share the same scope and the flag must match.
--consumer-scope=cluster+Note: consumer-scope must match the scope of all permission claims. Mixed scopes are not allowed (all cluster-scoped or all namespace-scoped).
+--- `96-96`: **Tighten wording.** Minor grammar/polish. ```diff -9. Now we gonna initiate consumer: +9. Now we are going to initiate the consumer:
123-123: Add language to fenced code block (markdownlint MD040).Set bash for the “Create objects” block.
-``` +```bash
139-145: Use the correct remote namespace instead of a hard-coded example.The example uses kube-bind-lxj5k-default, which doesn’t match the earlier remote namespace (e.g., kube-bind-wg2tb). Replace with a placeholder or dynamically resolve it to prevent copy/paste errors.
-# some claimed objects -kubectl create cm provider -n kube-bind-lxj5k-default -kubectl label cm provider app=wildwest -n kube-bind-lxj5k-default +# some claimed objects (replace <remote-namespace> with your actual one) +kubectl create cm provider -n <remote-namespace> +kubectl label cm provider app=wildwest -n <remote-namespace> kubectl create cm consumer -n default kubectl label cm consumer app=wildwest -n defaultOptionally, show a helper to discover it:
- If you used --remote-namespace earlier, reuse that value.
- Or list namespaces and pick the kube-bind-*-default one.
40-40: Be consistent with kubectl vs k alias.You mix k and kubectl. Prefer one consistently or add a note that k is an alias for kubectl.
Also applies to: 63-63
contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (1)
220-311: PermissionClaims schema looks solid; immutability and XOR rule are correct.
- Selector XOR validation is correctly enforced.
- Array immutability is good.
One small wording fix in the resource description.
Apply this diff to fix the article grammar:
- not provided by an service binding export. + not provided by a service binding export.backend/http/handler.go (2)
383-415: Fix potential type method call and add deterministic sorting.
- If InformerScope lacks a String() method, this won’t compile. Use string(h.scope).
- Sort results for stable UI.
Apply these diffs:
- if !strings.EqualFold(h.scope.String(), string(item.Spec.Scope)) && h.scope != kubebindv1alpha2.ClusterScope { + if !strings.EqualFold(string(h.scope), string(item.Spec.Scope)) && h.scope != kubebindv1alpha2.ClusterScope { continue }- result = append(result, UISchema{ + result = append(result, UISchema{ Name: item.GetName(), Kind: item.Spec.Names.Kind, Scope: string(item.Spec.Scope), Version: ver, Group: item.Spec.Group, // Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it. Resource: item.Spec.Names.Plural, SessionID: sessionID, }) } +// stabilize UI ordering +sort.Slice(result, func(i, j int) bool { + if result[i].Group == result[j].Group { + return result[i].Resource < result[j].Resource + } + return result[i].Group < result[j].Group +})And add the import:
import ( + "sort" )
543-573: Duplicate schema listing logic; consider extracting a shared helper.getBackendDynamicResource duplicates reconciler.getExportedSchemas. Extract a reusable helper to DRY and keep behavior consistent.
deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (1)
224-315: PermissionClaims schema/validation look good; minor wording fix.Schema shape, XOR rule, and immutability are correct. Fix a small grammar issue.
Apply this diff:
- not provided by an service binding export. + not provided by a service binding export.backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (4)
82-131: Reuse listing logic across components.getExportedSchemas duplicates handler.getBackendDynamicResource. Centralize into a shared helper to avoid drift.
151-153: Avoid string-matching errors; use typed helpers.Replace string contains check with api/meta IsNoMatchError.
Apply this diff:
+ apimeta "k8s.io/apimachinery/pkg/api/meta" @@ - obj, err := r.getBoundSchema(ctx, cl, boundSchema.Namespace, boundSchema.Name) - if err != nil && !apierrors.IsNotFound(err) && !strings.Contains(err.Error(), "no matches for kind") { + obj, err := r.getBoundSchema(ctx, cl, boundSchema.Namespace, boundSchema.Name) + if err != nil && !apierrors.IsNotFound(err) && !apimeta.IsNoMatchError(err) { return err }
272-286: Tighten message copy when no schemas found.The message reads “SchemaNotFound not found”. Make it clear and consistent.
Apply this diff:
- conditions.MarkFalse( + conditions.MarkFalse( req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, "SchemaNotFound", conditionsapi.ConditionSeverityError, - "SchemaNotFound not found", + "no exported schemas found", ) - return fmt.Errorf("no exported schemas found") + return fmt.Errorf("no exported schemas found")
306-315: Clarify “DifferentScopes” messages and error.Avoid mixed terminology (“claimed resources”) and unused format args.
Apply this diff:
- if boundSchema.Spec.Scope != first { - conditions.MarkFalse(req, + if boundSchema.Spec.Scope != first { + conditions.MarkFalse(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady, "DifferentScopes", conditionsapi.ConditionSeverityError, - "Different scopes found: %v", - boundSchema.Spec.Scope, + "Different scopes found", ) - return fmt.Errorf("different scopes found for claimed resources: %v", boundSchema.Name) + return fmt.Errorf("different scopes found across requested resources") }pkg/konnector/controllers/contextstore/contextstore.go (1)
17-20: Promote comment to package docUse a proper package doc comment so it shows up in Go doc.
-// contextstore allows to manage and track context per controllers, stored at the different levels of the controller hierarchy: -// APIServiceExport - schemas and permissionClaims - each schema runs its own gvr controller and each permissionClaim runs its own controller. -// Context are stored at the APIServiceExport level. +// Package contextstore manages and tracks controller contexts at different levels of the controller hierarchy. +// APIServiceExport: schemas and permissionClaims — each schema runs its own GVR controller and each permissionClaim runs its own controller. +// Contexts are stored at the APIServiceExport level. package contextstorepkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (5)
60-60: Typo: resourc → resourceMinor copy fix in comment.
- syncStore contextstore.Store // by APIServiceExport name. This includes same ctx for resourc claims and crds. + syncStore contextstore.Store // by APIServiceExport name. This includes same ctx for resource claims and CRDs.
90-90: Use NewKey helper for consistencyBuild the export key via contextstore.NewKey to avoid ad-hoc concatenation.
- exportKey := contextstore.Key(namespace + "." + name) // Key for the export + exportKey := contextstore.NewKey(namespace, name) // Key for the export
283-287: Remove slices.Collect around maps.KeysUnnecessary and may not compile depending on Go version.
- logger.V(2).Info("Synced informers", "key", key, "consumer", slices.Collect(maps.Keys(consumerSynced))) + logger.V(2).Info("Synced informers", "key", key, "consumer", maps.Keys(consumerSynced)) - logger.V(2).Info("Synced informers", "key", key, "provider", slices.Collect(maps.Keys(providerSynced))) + logger.V(2).Info("Synced informers", "key", key, "provider", maps.Keys(providerSynced))
471-492: Claims: remove slices.Collect usage in logsSame here; use maps.Keys directly.
- consumerSynced := defaultConsumerInf.WaitForCacheSync(ctxWithCancel.Done()) - logger.V(2).Info("Synced consumer informers", "consumer", slices.Collect(maps.Keys(consumerSynced)), "key", claimKey) + consumerSynced := defaultConsumerInf.WaitForCacheSync(ctxWithCancel.Done()) + logger.V(2).Info("Synced consumer informers", "consumer", maps.Keys(consumerSynced), "key", claimKey) ... - providerSynced := defaultProviderInf.WaitForCacheSync(ctxWithCancel.Done()) - logger.V(2).Info("Synced provider informers", "provider", slices.Collect(maps.Keys(providerSynced)), "key", claimKey) + providerSynced := defaultProviderInf.WaitForCacheSync(ctxWithCancel.Done()) + logger.V(2).Info("Synced provider informers", "provider", maps.Keys(providerSynced), "key", claimKey)
22-24: Drop slices import and use maps.Keys directlygo.mod uses go 1.24.0 so slices.Collect compiles, but wrapping maps.Keys is redundant — maps.Keys already returns a slice. Replace slices.Collect(maps.Keys(...)) with maps.Keys(...) and remove the slices import.
Locations to update:
- pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go — lines 283, 286, 477, 488
- backend/controllers/clusterbinding/clusterbinding_reconcile.go — line 174
Suggested import diff:
-import ( +import ( "context" "fmt" - "maps" - "slices" + "maps" "strings" "time"Suggested replacements:
-logger.V(2).Info("Synced informers", "key", key, "consumer", slices.Collect(maps.Keys(consumerSynced))) +logger.V(2).Info("Synced informers", "key", key, "consumer", maps.Keys(consumerSynced)) -logger.V(2).Info("Synced informers", "key", key, "provider", slices.Collect(maps.Keys(providerSynced))) +logger.V(2).Info("Synced informers", "key", key, "provider", maps.Keys(providerSynced))Also update the two permission-claims occurrences the same way.
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (7)
70-73: Don’t overwrite providerNamespace in logs with status.namespaceYou’re replacing the original providerNamespace value with sn.Status.Namespace. Drop this override to avoid confusing logs.
- logger = logger.WithValues("providerNamespace", sn.Status.Namespace)
118-121: Fix log: downstreamNamespace should be consumerNSUse the consumer namespace in the log for clarity.
- logger.Info("Creating missing downstream object", "downstreamNamespace", providerNamespace, "downstreamName", providerObj.GetName()) + logger.Info("Creating missing downstream object", "downstreamNamespace", consumerNS, "downstreamName", providerObj.GetName())
148-156: Fix log wording: deleting upstream object (provider side)You’re deleting the provider object; call it “upstream” to stay consistent with previous logs.
- logger.Info("Owner copy of the object is gone, deleting downstream object", "name", name, "namespace", providerNamespace) + logger.Info("Owner copy is gone, deleting upstream object", "name", name, "namespace", providerNamespace)
158-165: Log says “annotation” but code sets a labelCorrect the log message.
- logger.Info("setting owner annotation for Consumer object") + logger.Info("setting owner label for Consumer object")
175-179: Avoid shadowing providerObj and preserve resourceVersion on provider updateShadowing is confusing; also set RV if updateProviderObject performs Update.
- providerObj := candidateFromOwnerObj(providerNamespace, providerObj) - if !equality.Semantic.DeepEqual(providerObj, candidate) { + sanitizedProvider := candidateFromOwnerObj(providerNamespace, providerObj) + if !equality.Semantic.DeepEqual(sanitizedProvider, candidate) { logger.Info("updating consumer owned object at provider") - return r.updateProviderObject(ctx, candidate) + candidate.SetResourceVersion(providerObj.GetResourceVersion()) + return r.updateProviderObject(ctx, candidate) }
208-216: Remove duplicate SetNamespace call in candidateFromOwnerObjSetNamespace is called twice; keep one.
candidate.SetOwnerReferences(nil) candidate.SetFinalizers(nil) - candidate.SetNamespace(downstreamNS) candidate.SetCreationTimestamp(v1.Time{})
238-268: Owner enum comparisons: verify String() availability; prefer string(...) for robustnessIf kubebindv1alpha2.Owner does not implement String(), this won’t compile. Comparing against string(...) avoids that dependency.
- switch ownerLabel { - case kubebindv1alpha2.OwnerProvider.String(): + switch ownerLabel { + case string(kubebindv1alpha2.OwnerProvider): return kubebindv1alpha2.OwnerProvider, nil - case kubebindv1alpha2.OwnerConsumer.String(): + case string(kubebindv1alpha2.OwnerConsumer): return kubebindv1alpha2.OwnerConsumer, nil } @@ - switch ownerLabel { - case kubebindv1alpha2.OwnerProvider.String(): + switch ownerLabel { + case string(kubebindv1alpha2.OwnerProvider): return kubebindv1alpha2.OwnerProvider, nil - case kubebindv1alpha2.OwnerConsumer.String(): + case string(kubebindv1alpha2.OwnerConsumer): return kubebindv1alpha2.OwnerConsumer, nil }Additionally, when both objects exist but are unlabeled, consider a deterministic tie-breaker (e.g., older CreationTimestamp wins) and then label both sides accordingly. This aligns with “auto‑label owner if not specified.”
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
92-95: Deep‑copy the selector to avoid external mutation side‑effects.Keep the informer’s filtering immutable even if the original selector pointer is modified later.
@@ - inf := DynamicMultiNamespaceInformer{ + var ls *metav1.LabelSelector + if labelSelector != nil { + ls = labelSelector.DeepCopy() + } + inf := DynamicMultiNamespaceInformer{ gvr: gvr, - labelSelector: labelSelector, + labelSelector: ls, providerNamespace: providerNamespace, providerDynamicClient: providerDynamicClient, serviceNamespaceInformer: serviceNamespaceInformer,
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (12)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(6 hunks)backend/http/handler.go(4 hunks)contrib/kcp/README.md(6 hunks)contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(3 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go
- contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-22T13:32:29.471Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.471Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
deploy/crd/kube-bind.io_apiserviceexportrequests.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlpkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gocontrib/kcp/README.md
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
deploy/crd/kube-bind.io_apiserviceexportrequests.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlpkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-22T13:20:49.933Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.933Z
Learning: In kube-bind, there are two different ResourceGroupName() methods: BoundSchema.ResourceGroupName() for CRDs (always non-empty groups) uses simple fmt.Sprintf formatting, while APIServiceExportRequestResource.ResourceGroupName() for export requests handles empty groups by converting to "core". BoundSchema is exclusively for CRDs which cannot have empty API groups per Kubernetes validation.
Applied to files:
contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlbackend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gocontrib/kcp/README.md
📚 Learning: 2025-09-23T12:28:06.068Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.068Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts after removing them from the store. The Cancel function is extracted and called outside the lock, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/contextstore/contextstore.go
📚 Learning: 2025-09-23T12:28:06.068Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.068Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts before removing them from the store. The Cancel function is called internally, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/contextstore/contextstore.go
📚 Learning: 2025-09-23T12:27:47.807Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.807Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. The Delete method also cancels contexts before removal. No explicit Cancel() calls are needed when using these methods.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/contextstore/contextstore.go
📚 Learning: 2025-09-23T12:27:47.807Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.807Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. No explicit Cancel() calls are needed when using BulkDeletePrefixed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/contextstore/contextstore.go
📚 Learning: 2025-09-22T13:20:49.933Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.933Z
Learning: In kube-bind, BoundSchema.ResourceGroupName() is only used for CRDs (Custom Resource Definitions), and CRDs must always have non-empty API groups. Therefore, handling empty groups (core API group "") is not necessary in this context, unlike general Kubernetes GroupResource handling.
Applied to files:
backend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
🧬 Code graph analysis (5)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
OwnerProvider(185-185)OwnerConsumer(187-187)Owner(181-181)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (9)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(40-46)Key(27-27)NewKey(33-38)SyncContext(53-57)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(52-180)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-146)Resource(148-148)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
NewController(57-223)pkg/konnector/controllers/cluster/serviceexport/status/status_controller.go (1)
NewController(51-177)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(171-178)Selector(135-144)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(305-308)NewDynamicMultiNamespaceInformer(77-104)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ClusterScope(62-62)ExportedSchemas(32-32)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (5)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
BoundSchema(41-47)ExportedSchemas(32-32)InformerScope(59-59)sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
Selector(135-144)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(147-163)PermissionClaim(171-178)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
🪛 markdownlint-cli2 (0.18.1)
contrib/kcp/README.md
123-123: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: verify
- GitHub Check: lint
- GitHub Check: go-test-e2e
- GitHub Check: go-test
🔇 Additional comments (20)
contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (1)
419-421: Storage version switch to v1alpha2 is appropriate.Marking v1alpha2 as storage:true and keeping v1alpha1 as storage:false is the right move for versioning.
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (4)
55-66: Correct ordering: ensure BoundSchemas before validation.This sequencing makes sense and prevents validation on missing local schemas.
241-242: Propagating PermissionClaims is correct.Mirroring request claims into export spec aligns with the design.
336-344: Claimable API guard is correct for v1alpha2.Matches the current ClaimableAPIs set (ConfigMaps, Secrets, ServiceAccounts).
If we plan cluster-scoped exports, confirm future ClaimableAPIs alignment (all current are namespace-scoped per design).
320-333: APIServiceExportConditionPermissionClaim is correct to use on the requestThe API type comment in sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go states this condition describes permission claims "requested in the APIServiceExport and APIServiceExportRequest", so using APIServiceExportConditionPermissionClaim in backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go is appropriate.
backend/http/handler.go (1)
385-386: No change required — InformerScope.String() present.
InformerScope is declared astype InformerScope stringand implementsfunc (in InformerScope) String() stringin sdk/apis/kubebind/v1alpha2/boundchema_types.go.pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (3)
36-36: Good addition: keep APIServiceExport on reconciler for owner refsStoring the export enables correct controller ownership on created objects.
54-56: Return error instead of panicking when apiServiceExport is nilThis aligns with earlier feedback and avoids crashing controllers.
69-71: OwnerReferences wiring looks correctUsing NewControllerRef with APIServiceExport ensures GC/ownership are set properly.
pkg/konnector/controllers/contextstore/contextstore.go (3)
33-38: NewKey trailing dot fix looks goodAvoids surprising prefixes when suffix is empty.
96-103: Avoid calling Cancel() while holding the mutexCurrent code defers cancel but still captures it while locked; prefer extracting cancel, unlock, then invoke cancel. Prevents potential deadlocks or re-entrancy.
func (c *contextStore) Delete(key Key) { - c.lock.Lock() - if ctx, ok := c.store[key]; ok { - defer ctx.Cancel() - delete(c.store, key) - } - c.lock.Unlock() + var cancel func() + c.lock.Lock() + if ctx, ok := c.store[key]; ok { + cancel = ctx.Cancel + delete(c.store, key) + } + c.lock.Unlock() + if cancel != nil { + cancel() + } }
105-126: Bulk delete cancels under lock — move cancels outside critical sectionCancelling within the lock can block and increase contention; extract cancels, delete under lock, then cancel after unlock.
func (c *contextStore) BulkDeletePrefixed(prefix Key) []SyncContext { - c.lock.Lock() - defer c.lock.Unlock() - - var deleted []SyncContext - var keysToDelete []Key - - for k, v := range c.store { - if strings.HasPrefix(k.String(), prefix.String()) { - keysToDelete = append(keysToDelete, k) - deleted = append(deleted, v) - } - } - - for _, k := range keysToDelete { - ctx := c.store[k] - ctx.Cancel() - delete(c.store, k) - } - - return deleted + var deleted []SyncContext + var keysToDelete []Key + var cancels []func() + + c.lock.Lock() + for k, v := range c.store { + if strings.HasPrefix(k.String(), prefix.String()) { + keysToDelete = append(keysToDelete, k) + deleted = append(deleted, v) + } + } + for _, k := range keysToDelete { + if ctx, ok := c.store[k]; ok { + cancels = append(cancels, ctx.Cancel) + delete(c.store, k) + } + } + c.lock.Unlock() + + for _, cancel := range cancels { + if cancel != nil { + cancel() + } + } + return deleted }pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (2)
145-146: Scope derivation: confirm behavior when no schemas presentisClusterScoped is derived from processed schemas. If an export has only permission claims (no resources), it stays false. That’s fine for current namespaced claims (SA/CM/Secret), but will misbehave if cluster-scoped claims are introduced without schemas. Consider deriving from claims when no schemas exist.
127-132: Fix incorrect key when BoundSchema is missingSecond argument should be export name, not the schema name again. Current code constructs namespace.schema.schema.
- key := contextstore.NewKey(namespace, name, name) + key := contextstore.NewKey(namespace, export.Name, name)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (3)
138-146: Set resourceVersion before update (if using Update, not Patch)If updateConsumerObject performs a Kubernetes Update, it requires metadata.resourceVersion. Set it from the existing consumer object.
candidate := candidateFromOwnerObj(consumerNS, providerObj) current := candidateFromOwnerObj(consumerNS, consumerObj) if !equality.Semantic.DeepEqual(candidate, current) { logger.Info("Updating downstream object data", "downstreamNamespace", consumerNS, "downstreamName", consumerObj.GetName()) + // preserve RV for Update semantics + candidate.SetResourceVersion(consumerObj.GetResourceVersion()) if _, err := r.updateConsumerObject(ctx, candidate); err != nil { logger.Error(err, "error updating consumer object") return err } }If updateConsumerObject actually performs a Patch/Apply, this change is harmless but safe.
217-234: Tighten label/annotation filters; consider allowlist (tracked follow-up)Use HasPrefix for well-formed keys instead of Contains. Longer term, follow the allowlist approach discussed previously.
- for key, label := range obj.GetLabels() { - if strings.Contains(key, "claimed.internal.apis.kcp.io") { + for key, label := range obj.GetLabels() { + if strings.HasPrefix(key, "claimed.internal.apis.kcp.io/") { continue } labels[key] = label } @@ - for key, annotation := range obj.GetAnnotations() { - if strings.Contains(key, "kcp.io/cluster") { + for key, annotation := range obj.GetAnnotations() { + if strings.HasPrefix(key, "kcp.io/") { continue } annotations[key] = annotation }
55-75: consumerNS should remain sn.Name; sn.Status.Namespace is the provider/upstream namespaceAPIServiceNamespace doc/comment and repository usage show Status.Namespace is the service-provider (upstream) namespace and the APIServiceNamespace object name is the consumer namespace (see sdk/apis/kubebind/v1alpha2/apiservicenamespace_types.go:56–59). Current code is correct.
Likely an incorrect or invalid review comment.
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
26-26: Import looks good.Needed for LabelSelector conversion.
64-65: Field addition LGTM.Nil = no filtering, which matches desired behavior.
82-83: Confirm callers provide provider-controlled, immutable selectors.Ensure all call sites pass the provider-owned selector per API invariant; nil should be used to mean “no filter”.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (6)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (6)
118-121: Fix log namespace for downstream creationLog uses providerNamespace instead of consumerNS.
- if errors.IsNotFound(consumerErr) { - logger.Info("Creating missing downstream object", "downstreamNamespace", providerNamespace, "downstreamName", providerObj.GetName()) + if errors.IsNotFound(consumerErr) { + logger.Info("Creating missing downstream object", "downstreamNamespace", consumerNS, "downstreamName", providerObj.GetName())
209-216: Remove duplicate SetNamespace callNo behavior change, just cleanup.
candidate.SetOwnerReferences(nil) candidate.SetFinalizers(nil) - candidate.SetNamespace(downstreamNS) candidate.SetCreationTimestamp(v1.Time{})
241-268: Owner resolution fallback: consider requeue or deterministic defaultIf both objects exist without owner labels, determineOwner returns error and reconcile exits silently. Consider either:
- returning an error to requeue, or
- a deterministic default (e.g., prefer Provider).
If you prefer requeue, return a non-nil error here so workqueue retries. Confirm which behavior aligns with API guarantees.
150-157: Clarify log: deleting provider object, not downstreamMessage says “downstream object” but this path deletes the provider object.
- logger.Info("Owner copy of the object is gone, deleting downstream object", "name", name, "namespace", providerNamespace) + logger.Info("Owner copy is gone, deleting provider object", "providerName", name, "providerNamespace", providerNamespace)
159-166: Log wording: it’s a label, not an annotationMinor log accuracy.
- logger.Info("setting owner annotation for Consumer object") + logger.Info("setting owner label for Consumer object")
36-47: API consistency: consider ctx on provider getters for symmetrygetProviderObject/deleteProviderObject lack context; consider adding ctx for cancellation symmetry with consumer ops.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: go-test-e2e
- GitHub Check: lint
- GitHub Check: go-test
- GitHub Check: verify
🔇 Additional comments (1)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (1)
218-234: Metadata filtering policy (follow-up exists)Further allow/deny-listing of labels/annotations (e.g., kubectl/argocd managed keys) would be useful; prior review suggested this as a follow-up.
6661524 to
c80b305
Compare
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
164-180: Nil pointer on NotFound path when logging sns.Status.Namespace.When errors.IsNotFound(err), sns is nil; later logs access sns.Status.Namespace causing panic.
Apply:
- if errors.IsNotFound(err) || sns.Status.Namespace == "" { - if sns == nil { - logger.V(2).Info("APIServiceNamespace disappeared") - } else { - logger.V(2).Info("APIServiceNamespace disappeared", "namespace", sns.Status.Namespace) - } + if errors.IsNotFound(err) || sns.Status.Namespace == "" { + if sns == nil { + logger.V(2).Info("APIServiceNamespace disappeared") + } else { + logger.V(2).Info("APIServiceNamespace disappeared", "namespace", sns.Status.Namespace) + } inf.lock.Lock() defer inf.lock.Unlock() if cancel, found := inf.namespaceCancel[name]; found { - logger.V(2).Info("stopping informer", "namespace", sns.Status.Namespace) + if sns != nil { + logger.V(2).Info("stopping informer", "namespace", sns.Status.Namespace) + } else { + logger.V(2).Info("stopping informer", "namespace", name) + } delete(inf.namespaceCancel, name) delete(inf.namespaceInformers, name) cancel() } return }
🧹 Nitpick comments (29)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
155-157: Avoid reassigning status claims on every schema iteration
We already have the export object outside the loop, soreferencePermissionClaimswill do the same assignment for every schema. Move the call outside the loop to avoid redundant work (and future surprises if the helper ever stops being a no-op).pkg/indexers/servicebinding.go (1)
44-56: Deduplicate index keys to avoid redundant entriesIf multiple BoundSchemas resolve to the same resource.group, you'll emit duplicates. Dedup to keep the index lean.
func IndexAPIServiceBindingByCRD(obj any) ([]string, error) { binding, ok := obj.(*kubebindv1alpha2.APIServiceBinding) if !ok { return nil, nil } - keys := make([]string, 0, len(binding.Status.BoundSchemas)) - for _, bound := range binding.Status.BoundSchemas { - keys = append(keys, fmt.Sprintf("%s.%s", bound.Resource, bound.Group)) - } + keys := make([]string, 0, len(binding.Status.BoundSchemas)) + seen := make(map[string]struct{}, len(binding.Status.BoundSchemas)) + for _, bound := range binding.Status.BoundSchemas { + key := fmt.Sprintf("%s.%s", bound.Resource, bound.Group) + if _, ok := seen[key]; ok { + continue + } + seen[key] = struct{}{} + keys = append(keys, key) + } return keys, nil }backend/http/handler.go (2)
383-415: Sort UI schemas for deterministic renderingIterating a map yields random order; sort before templating for stable UI and tests.
result := make([]UISchema, 0, len(exportedSchemas)) for _, item := range exportedSchemas { if !strings.EqualFold(h.scope.String(), string(item.Spec.Scope)) && h.scope != kubebindv1alpha2.ClusterScope { continue } ... result = append(result, UISchema{ Name: item.GetName(), Kind: item.Spec.Names.Kind, Scope: string(item.Spec.Scope), Version: ver, Group: item.Spec.Group, // Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it. Resource: item.Spec.Names.Plural, SessionID: sessionID, }) } + // sort by group, then resource, then version for determinism + sort.Slice(result, func(i, j int) bool { + if result[i].Group != result[j].Group { + return result[i].Group < result[j].Group + } + if result[i].Resource != result[j].Resource { + return result[i].Resource < result[j].Resource + } + return result[i].Version < result[j].Version + })Add import:
import "sort"
563-570: Be tolerant to per-item conversion errorsFailing the whole list on one bad item degrades UX. Log and skip instead.
for _, item := range list.Items { boundSchema, err := helpers.UnstructuredToBoundSchema(item) if err != nil { - return nil, err + klog.FromContext(ctx).Error(err, "skipping invalid BoundSchema") + continue } boundSchemas[boundSchema.ResourceGroupName()] = boundSchema }sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
83-90: Return scope with GVR (optional)Callers often need scope to construct informers. Consider returning (schema.GroupVersionResource, apiextensionsv1.ResourceScope).
deploy/crd/kube-bind.io_apiservicebindings.yaml (1)
385-406: Clarify namedResource: plural semantics and typo
- Description says “a single resource” but the field is an array.
- Typo: “Namespaces” vs “Namespace”.
- Consider declaring list type explicitly.
- namedResource: - description: NamedResource is a shorthand for selecting - a single resource by name and namespace. + namedResource: + description: NamedResource is a shorthand for selecting + one or more resources by name and namespace. items: description: NamedResource selects a specific resource by name and namespace. properties: name: description: |- Name is the name of the resource. Name matches the metadata.name field of the underlying object. type: string namespace: description: |- - Namespace represents namespace where an object of the given group/resource may be managed. - Namespaces matches against the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces. - Namespaces field is ignored for namespaced isolation mode. + Namespace represents the namespace where an object of the given group/resource may be managed. + Namespace matches the metadata.namespace field. A value of "*" matches namespaced objects across all namespaces. + Namespace field is ignored for namespaced isolation mode. type: string required: - name type: object type: array + x-kubernetes-list-type: atomicpkg/resources/resources_test.go (1)
12-18: Add wildcard namespace (“*”) test to cover spec semantics.NamedResource.Namespace supports “*” to match across namespaces, but there’s no test. Add a case to prevent regressions.
Apply this diff to add coverage:
@@ func TestSelector_IsClaimed(t *testing.T) { }, + { + name: "named resource with wildcard namespace should match any namespace", + selector: kubebindv1alpha2.Selector{ + NamedResource: []kubebindv1alpha2.NamedResource{ + { + Name: "test-obj", + Namespace: "*", + }, + }, + }, + obj: &unstructured.Unstructured{ + Object: map[string]any{ + "metadata": map[string]any{ + "name": "test-obj", + "namespace": "some-ns", + }, + }, + }, + want: true, + },contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml (1)
490-594: Enforce uniqueness of group/resource pairs in permissionClaims.Prevent duplicate claims via an additional CEL validation alongside immutability.
Apply this diff under the existing x-kubernetes-validations for permissionClaims:
permissionClaims: @@ type: array x-kubernetes-validations: - message: permissionClaims are immutable rule: self == oldSelf + - message: permissionClaims must target unique group/resource pairs + rule: self.map(c, (c.group + "/" + c.resource)).distinct().size() == self.size()pkg/resources/resources.go (2)
23-33: Avoid variable shadowing for readability.Shadowing parameter name ‘selector’ makes the code harder to follow.
Apply this diff:
- selector, err := metav1.LabelSelectorAsSelector(selector.LabelSelector) + lsel, err := metav1.LabelSelectorAsSelector(selector.LabelSelector) @@ - labelSelectorMatches = selector.Matches(labels.Set(l)) + labelSelectorMatches = lsel.Matches(labels.Set(l))
12-16: Guard against nil obj to avoid panics.Defensive check prevents NPEs if callers pass nil.
Apply this diff:
func IsClaimed(selector kubebindv1alpha2.Selector, obj *unstructured.Unstructured) bool { + if obj == nil { + return false + }pkg/indexers/serviceexport.go (2)
40-51: Fix docstring to match function name; consider de-duping logic
- The comment says “IndexServiceExportByBoundSchema is a controller-runtime compatible indexer function”, but the function is named IndexServiceExportByBoundSchemaControllerRuntime. Update the comment to avoid confusion.
- The loop building names duplicates the logic below. Factor out a small helper to prevent drift.
Apply this diff to fix the docstring and delegate to a shared helper:
-// IndexServiceExportByBoundSchema is a controller-runtime compatible indexer function. +// IndexServiceExportByBoundSchemaControllerRuntime is a controller-runtime compatible indexer function. func IndexServiceExportByBoundSchemaControllerRuntime(obj client.Object) []string { export, ok := obj.(*v1alpha2.APIServiceExport) if !ok { return nil } - names := make([]string, 0, len(export.Spec.Resources)) - for _, res := range export.Spec.Resources { - names = append(names, res.ResourceGroupName()) - } - return names + return resourceGroupNamesFromExport(export) }Add this helper outside the shown hunk:
// resourceGroupNamesFromExport returns "resource.group" keys for all export resources. func resourceGroupNamesFromExport(export *v1alpha2.APIServiceExport) []string { names := make([]string, 0, len(export.Spec.Resources)) for _, res := range export.Spec.Resources { names = append(names, res.ResourceGroupName()) } return names }
53-65: Correct docstring and reuse helper for client-go indexer variant
- This variant is client-go compatible (any) not controller-runtime. Update the comment.
- Reuse the helper to avoid duplicating the loop.
-// IndexServiceExportByBoundSchema is a controller-runtime compatible indexer function. +// IndexServiceExportByBoundSchema is a client-go compatible indexer function. func IndexServiceExportByBoundSchema(obj any) ([]string, error) { export, ok := obj.(*v1alpha2.APIServiceExport) if !ok { return nil, nil } - names := make([]string, 0, len(export.Spec.Resources)) - for _, res := range export.Spec.Resources { - names = append(names, res.ResourceGroupName()) - } - return names, nil + return resourceGroupNamesFromExport(export), nil }contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (1)
303-404: Enforce selector non-emptiness in PermissionClaimsAs written, selector is required but can be empty ({}), unintentionally granting an all-objects selector. Add a CEL validation to require either labelSelector or at least one namedResource.
selector: description: Selector is a resource selector that selects objects of a GVR. properties: labelSelector: description: LabelSelector is a label selector that selects objects of a GVR. ... namedResource: description: NamedResource is a shorthand for selecting a single resource by name and namespace. ... - type: object + type: object + x-kubernetes-validations: + - rule: 'has(self.labelSelector) || size(self.namedResource) > 0' + message: 'selector must define either labelSelector or namedResource' required: - resource - selector type: object type: arraycontrib/kcp/README.md (2)
136-138: Fix quoting for nested jsonpath in command substitutionUse single quotes for -o jsonpath to avoid nested double-quote pitfalls.
-k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath="{.status.endpoints[0].url}")/clusters/*" api-resources -k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath="{.status.endpoints[0].url}")/clusters/*" get crd +k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath='{.status.endpoints[0].url}')/clusters/*" api-resources +k -s "$(kubectl get apiexportendpointslice kube-bind.io -o jsonpath='{.status.endpoints[0].url}')/clusters/*" get crdApply the same quoting pattern earlier where --server-url uses jsonpath.
111-116: Clarify secret retrieval stepThe text correctly warns the secret name differs. Suggest adding a direct discovery command to avoid guesswork.
-# name by running `kubectl get secret -n kube-bind` -kubectl get secret kubeconfig-pr2xk -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote.kubeconfig +# name by running: +# kubectl get secrets -n kube-bind -o jsonpath='{range .items[?(@.type=="kubeconfig")]}{.metadata.name}{"\n"}{end}' +kubectl get secret kubeconfig-pr2xk -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote.kubeconfigcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (1)
220-325: Selector validation: consider enforcing non-empty selectors.As written, an empty selector (no labelSelector and no namedResource) matches everything. If that’s intentional, fine; otherwise add a CEL to require at least one of labelSelector or namedResource.
Example:
+ x-kubernetes-validations: + - message: selector must specify at least one of labelSelector or namedResource + rule: has(self.selector) && (has(self.selector.labelSelector) || size(self.selector.namedResource) > 0)Please confirm desired behavior. Based on learnings
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (2)
235-263: Nil deref risk if apiServiceExport can be nil.OwnerReference creation dereferences c.apiServiceExport. If the lifecycle can ever pass nil here, this will panic. If it’s guaranteed non‑nil, add a short comment to document the assumption; otherwise guard it.
Example:
- OwnerReferences: []metav1.OwnerReference{ - *metav1.NewControllerRef(c.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport")), - }, + OwnerReferences: func() []metav1.OwnerReference { + if c.apiServiceExport == nil { + return nil + } + return []metav1.OwnerReference{*metav1.NewControllerRef(c.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport"))} + }(),Based on learnings
269-287: Blocking wait in worker is acceptable but add a max bound.PollUntilContextCancel blocks a worker indefinitely if readiness never flips. Add an upper bound or backoff to avoid starving workers on pathological cases.
Example:
-err = wait.PollUntilContextCancel(ctx, 500*time.Millisecond, true, func(ctx context.Context) (done bool, err error) { +err = wait.PollUntilContextTimeout(ctx, 500*time.Millisecond, 2*time.Minute, true, func(ctx context.Context) (done bool, err error) {test/e2e/bind/happy-case_test.go (1)
373-431: Assertions align with AND semantics; timeouts vary.The negative checks for label-only and unrelated secrets are correct. Consider harmonizing timeouts (some use 10m, others ForeverTestTimeout) to reduce flakiness.
deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (1)
224-329: Mirror selector non-empty validation (if intended).If empty selector should not grant blanket access, add a CEL rule requiring at least one of labelSelector or namedResource, to stay consistent with APIResourceSchema.
Example:
+ x-kubernetes-validations: + - message: selector must specify at least one of labelSelector or namedResource + rule: has(self.selector) && (has(self.selector.labelSelector) || size(self.selector.namedResource) > 0)Please confirm intent. Based on learnings
deploy/crd/kube-bind.io_apiserviceexports.yaml (1)
493-597: CRD: permissionClaims shape looks consistent; optional selector validation.Structure matches types. If you want to forbid blanket selectors, add a CEL rule requiring labelSelector or namedResource.
+ x-kubernetes-validations: + - message: selector must specify at least one of labelSelector or namedResource + rule: has(self.selector) && (has(self.selector.labelSelector) || size(self.selector.namedResource) > 0)Based on learnings
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
161-181: Role reconcile path is fine; minor shadowing nit.You re-declare role with := in the create branch; not harmful but can be simplified.
- role := &rbacv1.Role{ + role = &rbacv1.Role{
79-117: No cleanup on export removal; consider garbage collection.RBAC objects linger if an APIServiceExport is deleted. Consider labeling created Roles/Bindings and pruning ones whose export no longer exists.
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (1)
201-214: Remove duplicate namespace assignment in candidate builderNamespace is set twice; keep one.
Apply this diff:
func candidateFromOwnerObj(downstreamNS string, obj *unstructured.Unstructured) *unstructured.Unstructured { // clean up object candidate := obj.DeepCopy() candidate.SetUID("") candidate.SetResourceVersion("") candidate.SetNamespace(downstreamNS) candidate.SetManagedFields(nil) candidate.SetDeletionTimestamp(nil) candidate.SetDeletionGracePeriodSeconds(nil) candidate.SetOwnerReferences(nil) candidate.SetFinalizers(nil) - candidate.SetNamespace(downstreamNS) candidate.SetCreationTimestamp(v1.Time{})backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (2)
260-263: Fix structured logging for request deletionUse structured args instead of printf-style format string.
Apply this diff:
- logger.Info("Deleting service binding request %s/%s", req.Namespace, req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time)) + logger.Info("Deleting service binding request", "namespace", req.Namespace, "name", req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time))
280-289: Tweak condition messageMinor wording nit.
Apply this diff:
- "SchemaNotFound not found", + "Schema not found",pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (1)
90-91: Optional: use NewKey for exportKeyFor consistency with other keys.
Apply this diff:
- exportKey := contextstore.Key(namespace + "." + name) // Key for the export + exportKey := contextstore.NewKey(namespace, name) // Key for the exportsdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
132-142: Enforce exactly one of labelSelector or namedResourceAdd CEL XValidation to require one selector mechanism, and avoid both/none.
Apply this diff:
-// Selector is a resource selector that selects objects of a GVR. +// Selector is a resource selector that selects objects of a GVR. +// +kubebuilder:validation:XValidation:rule="has(self.labelSelector) != (size(self.namedResource) > 0)",message="set exactly one of labelSelector or namedResource" // Selectors are ANDed together if multiple are specified. type Selector struct {Note: size() is valid for lists; use has() for the object field.
180-182: Prefer “core” in String() for empty groupImproves messages for core APIs (configmaps, secrets, serviceaccounts).
Apply this diff:
func (r GroupResource) String() string { - return fmt.Sprintf("%s.%s", r.Resource, r.Group) + if r.Group == "" { + return fmt.Sprintf("%s.%s", r.Resource, "core") + } + return fmt.Sprintf("%s.%s", r.Resource, r.Group) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
Makefile(3 hunks)backend/controllers/clusterbinding/clusterbinding_reconcile.go(1 hunks)backend/controllers/serviceexport/serviceexport_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(6 hunks)backend/controllers/servicenamespace/servicenamespace_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(3 hunks)backend/http/handler.go(4 hunks)backend/kubernetes/resources/namespace.go(1 hunks)contrib/kcp/README.md(6 hunks)contrib/kcp/deploy/bootstrap.go(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml(1 hunks)contrib/kcp/deploy/examples/cowboy.yaml(1 hunks)contrib/kcp/deploy/examples/sheriff.yaml(1 hunks)contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)pkg/indexers/servicebinding.go(2 hunks)pkg/indexers/serviceexport.go(2 hunks)pkg/indexers/servicenamespace.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(4 hunks)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(6 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go(2 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(3 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)pkg/resources/resources.go(1 hunks)pkg/resources/resources_test.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go(2 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)sdk/apis/kubebind/v1alpha2/boundchema_types.go(4 hunks)sdk/apis/kubebind/v1alpha2/claimable_apis.go(1 hunks)sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go(8 hunks)test/e2e/bind/happy-case_test.go(16 hunks)test/e2e/framework/clients.go(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
- contrib/kcp/deploy/examples/sheriff.yaml
- pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go
- pkg/indexers/servicenamespace.go
- backend/controllers/serviceexportrequest/serviceexportrequest_controller.go
- contrib/kcp/deploy/bootstrap.go
- backend/controllers/serviceexport/serviceexport_controller.go
- pkg/konnector/controllers/contextstore/contextstore.go
- sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go
- test/e2e/framework/clients.go
- pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md
- contrib/kcp/deploy/examples/cowboy.yaml
- contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml
- contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml
- Makefile
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, there are two different ResourceGroupName() methods: BoundSchema.ResourceGroupName() for CRDs (always non-empty groups) uses simple fmt.Sprintf formatting, while APIServiceExportRequestResource.ResourceGroupName() for export requests handles empty groups by converting to "core". BoundSchema is exclusively for CRDs which cannot have empty API groups per Kubernetes validation.
Applied to files:
pkg/indexers/serviceexport.gopkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlsdk/apis/kubebind/v1alpha2/boundchema_types.gocontrib/kcp/deploy/examples/apiserviceexport-cluster.yamlbackend/http/handler.gosdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/controllers/clusterbinding/clusterbinding_reconcile.go
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gosdk/apis/kubebind/v1alpha2/boundchema_types.gobackend/http/handler.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gosdk/apis/kubebind/v1alpha2/apiserviceexport_types.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlcontrib/kcp/deploy/examples/apiserviceexport-cluster.yamldeploy/crd/kube-bind.io_apiserviceexports.yamltest/e2e/bind/happy-case_test.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.godeploy/crd/kube-bind.io_apiserviceexportrequests.yamlbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.godeploy/crd/kube-bind.io_apiservicebindings.yamlcontrib/kcp/README.md
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlpkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gosdk/apis/kubebind/v1alpha2/boundchema_types.gocontrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yamlpkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, BoundSchema.ResourceGroupName() is only used for CRDs (Custom Resource Definitions), and CRDs must always have non-empty API groups. Therefore, handling empty groups (core API group "") is not necessary in this context, unlike general Kubernetes GroupResource handling.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.gocontrib/kcp/deploy/examples/apiserviceexport-cluster.yamlbackend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/controllers/clusterbinding/clusterbinding_reconcile.go
📚 Learning: 2025-09-22T13:32:29.499Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.499Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gosdk/apis/kubebind/v1alpha2/apiserviceexport_types.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gopkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.gocontrib/kcp/deploy/examples/apiserviceexport-cluster.yamltest/e2e/bind/happy-case_test.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
test/e2e/bind/happy-case_test.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.godeploy/crd/kube-bind.io_apiservicebindings.yamlcontrib/kcp/README.md
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts after removing them from the store. The Cancel function is extracted and called outside the lock, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts before removing them from the store. The Cancel function is called internally, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. The Delete method also cancels contexts before removal. No explicit Cancel() calls are needed when using these methods.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. No explicit Cancel() calls are needed when using BulkDeletePrefixed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
🧬 Code graph analysis (21)
pkg/indexers/serviceexport.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
APIServiceBinding(64-74)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
pkg/resources/resources.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
Selector(134-142)NamedResource(145-159)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (2)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
ConditionType(48-48)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(186-193)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (6)
pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/indexers/servicenamespace.go (2)
ServiceNamespaceByNamespace(26-26)IndexServiceNamespaceByNamespace(29-36)pkg/konnector/controllers/contextstore/contextstore.go (1)
New(59-63)pkg/indexers/serviceexport.go (2)
ServiceExportByBoundSchema(27-27)IndexServiceExportByBoundSchema(54-65)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (3)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)NamedResource(145-159)GroupResource(162-178)Selector(134-142)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
InternalAPI(29-35)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
Conditions(92-92)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ClusterScope(62-62)ExportedSchemas(32-32)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
pkg/indexers/servicebinding.go (1)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
APIServiceBinding(64-74)
test/e2e/bind/happy-case_test.go (4)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)NamespacedScope(63-63)InformerScope(59-59)test/e2e/framework/clients.go (2)
KubeClient(39-43)BindClient(57-61)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
APIServiceExportRequest(46-60)PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)pkg/konnector/controllers/cluster/serviceexport/cluster-scoped/utils.go (2)
ExtractClusterNs(97-104)Prepend(33-35)
pkg/resources/resources_test.go (2)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
Selector(134-142)NamedResource(145-159)pkg/resources/resources.go (1)
IsClaimed(12-50)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
sdk/apis/kubebind/v1alpha2/register.go (1)
Resource(42-44)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (3)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (1)
Resource(145-145)sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
ClusterScope(62-62)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (9)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(40-46)Key(27-27)NewKey(33-38)SyncContext(53-57)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(53-184)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-143)Resource(145-145)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
NewController(57-223)pkg/konnector/controllers/cluster/serviceexport/status/status_controller.go (1)
NewController(51-177)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(305-308)NewDynamicMultiNamespaceInformer(77-104)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (9)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-143)Resource(145-145)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
NewController(57-223)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)pkg/resources/resources.go (1)
IsClaimed(12-50)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (7)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
BoundSchema(41-47)ExportedSchemas(32-32)InformerScope(59-59)sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (6)
Selector(134-142)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(162-178)APIServiceExportRequestPhaseFailed(218-218)PermissionClaim(186-193)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
ConditionSeverityError(31-31)sdk/apis/third_party/conditions/util/conditions/getter.go (1)
GetMessage(94-99)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
backend/controllers/clusterbinding/clusterbinding_reconcile.go (1)
sdk/apis/kubebind/v1alpha2/register.go (1)
GroupName(32-32)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (2)
sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
OwnerProvider(200-200)OwnerConsumer(202-202)Owner(196-196)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (6)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(53-184)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/resources/resources.go (1)
IsClaimed(12-50)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)
sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(186-193)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: lint
- GitHub Check: go-test-e2e
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
backend/kubernetes/resources/namespace.go (1)
35-54: Consider removing the legacy key even when already migrated
We only clean uplegacyIdentityAnnotationKeywhen the new annotation is missing or mismatched. If a cluster operator manually fixed the new key earlier, the legacy key will linger forever. Dropping the legacy key whenever it’s present (and matches the expected id) would guarantee eventual cleanup with zero extra cost.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
backend/kubernetes/resources/namespace.go(2 hunks)backend/kubernetes/resources/namespace_test.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
🧬 Code graph analysis (1)
backend/kubernetes/resources/namespace_test.go (1)
backend/kubernetes/resources/namespace.go (2)
IdentityAnnotationKey(31-31)CreateNamespace(56-88)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: lint
- GitHub Check: go-test-e2e
- GitHub Check: go-test
🔇 Additional comments (2)
backend/kubernetes/resources/namespace.go (1)
78-84: Thanks for sealing the upgrade gap
Accepting either annotation during the AlreadyExists reconciliation and invoking the legacy handler keeps upgrades from breaking while still converging on the new key. Looks solid.backend/kubernetes/resources/namespace_test.go (1)
125-297: Great coverage on the migration scenarios
The intercepted fake client plus the table-driven cases hit both the happy path and the tricky legacy permutations, which should keep the regression we saw from resurfacing. Nicely done.
6ca229e to
d8c9fa2
Compare
There was a problem hiding this comment.
Actionable comments posted: 9
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
85-90: Clarify CRD group comment (core group not allowed for CRDs)The current comment suggests empty group (core) is valid; for CRDs, group must be non-empty. Adjust to avoid confusion.
- // group is the API group of the defined custom resource. Empty string means the - // core API group. The resources are served under `/apis/<group>/...` or `/api` for the core group. + // group is the API group of the defined custom resource. + // For CRDs, this must be non-empty (core API group "" is not permitted for CRDs). + // Resources are served under `/apis/<group>/...`.backend/controllers/servicenamespace/servicenamespace_reconcile.go (1)
266-268: Correct error message on update pathMessage says “create” on an update.
- if err := c.updateRoleBinding(ctx, client, binding); err != nil { - return fmt.Errorf("failed to create role binding %s/%s: %w", ns, objName, err) - } + if err := c.updateRoleBinding(ctx, client, binding); err != nil { + return fmt.Errorf("failed to update role binding %s/%s: %w", ns, objName, err) + }backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (3)
201-209: Set Succeeded when export already existsWhen APIServiceExport already exists, you MarkTrue but don’t set Phase=Succeeded, leaving stale Pending.
- } else { - // already exists; nothing to do - conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) - return nil - } + } else { + // already exists; nothing to do + conditions.MarkTrue(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) + req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseSucceeded + return nil + }
254-257: Bug: request flips to Failed after successThis unconditionally overrides Succeeded if the request is older than 1 minute, even after a successful export. Remove or guard this with an actual failure condition.
- if time.Since(req.CreationTimestamp.Time) > time.Minute { - req.Status.Phase = kubebindv1alpha2.APIServiceExportRequestPhaseFailed - req.Status.TerminalMessage = conditions.GetMessage(req, kubebindv1alpha2.APIServiceExportRequestConditionExportsReady) - } + // keep Succeeded; do not downgrade to Failed based on age alone
260-263: Fix structured logging (klog)klog.Info expects key/value pairs, not printf formatting.
- logger.Info("Deleting service binding request %s/%s", req.Namespace, req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time)) + logger.Info("Deleting service export request", "namespace", req.Namespace, "name", req.Name, "reason", "timeout", "age", time.Since(req.CreationTimestamp.Time))
🧹 Nitpick comments (34)
pkg/resources/resources.go (1)
39-49: Avoid shadowing the parameter name “selector”The short var decl reuses the name “selector”, shadowing the function parameter and hurting readability. Rename the local var.
- selector, err := metav1.LabelSelectorAsSelector(selector.LabelSelector) + ls, err := metav1.LabelSelectorAsSelector(selector.LabelSelector) if err != nil { return false } l := obj.GetLabels() if l == nil { l = make(map[string]string) } - labelSelectorMatches = selector.Matches(labels.Set(l)) + labelSelectorMatches = ls.Matches(labels.Set(l))backend/http/handler.go (2)
386-417: Deterministic UI orderingConsider sorting result for stable rendering/testing (e.g., by resource then group).
result := make([]UISchema, 0, len(exportedSchemas)) for _, item := range exportedSchemas { ... result = append(result, UISchema{ ... }) } + // sort deterministically by resource then group + sort.Slice(result, func(i, j int) bool { + if result[i].Resource == result[j].Resource { + return result[i].Group < result[j].Group + } + return result[i].Resource < result[j].Resource + })Also add:
import ( ... + "sort" )
566-575: Be resilient to a single bad item during conversionCurrently, one malformed item aborts the whole list. Prefer logging and skipping.
for _, item := range list.Items { boundSchema, err := helpers.UnstructuredToBoundSchema(item) if err != nil { - return nil, err + // skip bad item but continue with others + klog.FromContext(ctx).Error(err, "skipping malformed bound schema", "name", item.GetName()) + continue } boundSchemas[boundSchema.ResourceGroupName()] = boundSchema }test/e2e/framework/clients.go (1)
57-61: Mark helper and tighten failure context in testsCall t.Helper() so failures point to the caller, and add a nil check to avoid surprises if a nil client ever slips through.
func BindClient(t *testing.T, config *rest.Config) bindclientset.Interface { - c, err := bindclientset.NewForConfig(config) + t.Helper() + c, err := bindclientset.NewForConfig(config) require.NoError(t, err) + require.NotNil(t, c) return c }backend/kubernetes/resources/namespace.go (2)
43-51: Also drop the legacy key when both keys exist and matchIf both annotations are present and both equal id, the legacy key is left behind. Clean it up to be fully idempotent.
- if hasLegacy && (!hasCurrent || currentValue != id) && legacyValue == id { + if hasLegacy && legacyValue == id && (!hasCurrent || currentValue != id) { original := namespace.DeepCopy() - if namespace.Annotations == nil { - namespace.Annotations = map[string]string{} - } namespace.Annotations[IdentityAnnotationKey] = id delete(namespace.Annotations, legacyIdentityAnnotationKey) return cl.Patch(ctx, namespace, client.MergeFrom(original)) } + + // cleanup: both present and already equal -> remove legacy + if hasLegacy && hasCurrent && currentValue == id && legacyValue == id { + original := namespace.DeepCopy() + delete(namespace.Annotations, legacyIdentityAnnotationKey) + return cl.Patch(ctx, namespace, client.MergeFrom(original)) + }
69-85: AlreadyExists path with GenerateName is brittle (Get by empty name)This branch calls Get using namespace.Name, which is empty when Create used only GenerateName and failed. With GenerateName, apiserver should avoid collisions and not return AlreadyExists; if it does, the subsequent Get will likely fail. Either remove this branch or rework it to find an existing namespace by annotation (IdentityAnnotationKey/legacyIdentityAnnotationKey).
Proposed direction (summarized):
- On AlreadyExists, List Namespaces and pick one with annotation == id (accept legacy), then run handleLegacyAnnotations.
Would you like a concrete diff to switch to List+filter?
contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml (1)
490-594: Harden permissionClaims schema: uniqueness, enums, and item identityAdd CEL to ensure unique group/resource pairs; constrain matchExpressions.operator; and make namedResource a map keyed by namespace+name to prevent duplicates.
permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: description: |- PermissionClaim selects objects of a GVR that a service provider may request and that a consumer may accept and allow the service provider access to. properties: group: @@ selector: description: Selector is a resource selector that selects objects of a GVR. properties: labelSelector: @@ matchExpressions: @@ - items: + items: description: |- A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values. properties: key: @@ - operator: + operator: description: |- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string + type: string + enum: ["In","NotIn","Exists","DoesNotExist"] @@ namedResource: description: NamedResource is a shorthand for selecting a single resource by name and namespace. - items: + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: ["namespace","name"] + items: description: NamedResource selects a specific resource by name and namespace. @@ type: array x-kubernetes-validations: - message: permissionClaims are immutable rule: self == oldSelf + - message: permissionClaims must target unique group/resource pairs + rule: self.map(c, (c.group == "" ? "core" : c.group) + "/" + c.resource).distinct().size() == self.size()pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
56-66: Constructor surface keeps growing; consider Options structNew apiServiceExport param makes the signature long and easy to misuse. Consider an Options struct (or builder) to group related params and allow future growth without churn.
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
91-95: Add uniqueness validation and fix wording
- Enforce unique group/resource pairs via CEL (mirrors the schema suggestion).
- The comment says “records decisions”; Export spec should declare requested claims. Suggest wording tweak.
- // PermissionClaims records decisions about permission claims requested by the service provider. + // PermissionClaims declares permission claims requested by the service provider. // Access is granted per GroupResource. - // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable" + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable" + // +kubebuilder:validation:XValidation:rule="self.map(c, (c.group == '' ? 'core' : c.group) + '/' + c.resource).distinct().size() == self.size()",message="permissionClaims must target unique group/resource pairs" PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`If this uniqueness rule already exists elsewhere in the API or CRD generation, please point me to it; I don’t see it in this type.
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (2)
85-92: Consider watching UPDATE events for label changes.The controller only watches ADD events. If an object's labels are updated to match the claim selector after creation, this controller won't detect it and won't ensure the namespace exists.
Consider adding an UpdateFunc handler:
if _, err = consumerDynamicInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { c.enqueueConsumer(logger, obj) }, + UpdateFunc: func(oldObj, newObj interface{}) { + oldU := oldObj.(*unstructured.Unstructured) + newU := newObj.(*unstructured.Unstructured) + // Check if the object became claimed due to label changes + if !c.isClaimed(oldU) && c.isClaimed(newU) { + c.enqueueConsumer(logger, newObj) + } + }, }); err != nil {
269-284: Consider making the polling timeout configurable.The hardcoded 500ms polling interval and implicit timeout (from context) for waiting on APIServiceNamespace readiness could be problematic in high-latency environments or under heavy load.
Consider making these values configurable or at least defining them as constants:
+const ( + namespaceReadinessPollInterval = 500 * time.Millisecond + namespaceReadinessTimeout = 30 * time.Second +) // Wait until the servicenamespace status has been updated. -err = wait.PollUntilContextCancel(ctx, 500*time.Millisecond, true, func(ctx context.Context) (done bool, err error) { +pollCtx, cancel := context.WithTimeout(ctx, namespaceReadinessTimeout) +defer cancel() +err = wait.PollUntilContextCancel(pollCtx, namespaceReadinessPollInterval, true, func(ctx context.Context) (done bool, err error) {pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
153-159: Call referencePermissionClaims once, outside the schema loop.Permission claims are per-export, not per-schema. Reassigning on every iteration is redundant work and obscures intent.
Apply this diff:
for _, schema := range schemas { - if err := r.referenceBoundSchema(ctx, binding, schema.Name); err != nil { + if err := r.referenceBoundSchema(ctx, binding, schema.Name); err != nil { errs = append(errs, err) } - - if err := r.referencePermissionClaims(ctx, binding, export); err != nil { - errs = append(errs, err) - } if err := r.ensureCRDsFromBoundSchema(ctx, binding, schema); err != nil { errs = append(errs, err) } } + +// copy permission claims once per export +if err := r.referencePermissionClaims(ctx, binding, export); err != nil { + errs = append(errs, err) +}deploy/crd/kube-bind.io_apiservicebindings.yaml (1)
310-411: Tighten list semantics and clarify namedResource plurality.
- permissionClaims likely should be unique per (group, resource). Consider list-as-map to enforce uniqueness.
- selector.namedResource is typed as an array but the description says “a single resource”. Align doc vs. schema, and add list hints.
Suggested schema tweaks (illustrative patch):
permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: ... type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource @@ - namedResource: - description: NamedResource is a shorthand for selecting - a single resource by name and namespace. - items: + namedResource: + description: NamedResource selects one or more specific resources by name and (optionally) namespace. + items: properties: name: ... namespace: ... required: - name type: object type: array + x-kubernetes-list-type: atomicIf only a single named resource is desired, switch to a single object (not an array). Otherwise, keep array and update the description as shown.
contrib/kcp/README.md (1)
31-33: Add language to fenced code blocks (MD040) and minor UX polish.
- Several code fences lack a language; add bash for consistency and linting.
- Optional: use a variable for the kubeconfig secret to avoid hard-coding.
Apply these diffs:
-2. Bootstrap kcp: -```bash +2. Bootstrap kcp: +```bash cp .kcp/admin.kubeconfig .kcp/backend.kubeconfig ...-4. Run the backend:
-+4. Run the backend: +bash
k ws use :root:kube-bind
...
--consumer-scope=cluster@@ -Create objects: -``` +Create objects: +```bash kubectl apply -f contrib/kcp/deploy/examples/cowboy.yaml kubectl apply -f contrib/kcp/deploy/examples/sheriff.yamlAnd for the secret extraction example: ```diff -# name by running `kubectl get secret -n kube-bind` -kubectl get secret kubeconfig-pr2xk -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote.kubeconfig +# name by running `kubectl get secret -n kube-bind` +SECRET_NAME="$(kubectl get secret -n kube-bind -o name | sed -n 's|secret/\(kubeconfig-.*\)|\1|p' | head -n1)" +kubectl get secret "$SECRET_NAME" -n kube-bind -o jsonpath='{.data.kubeconfig}' | base64 -d > remote.kubeconfigAlso applies to: 39-55, 123-126
deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (2)
268-273: Constrain matchExpressions.operator to valid values.Add enum to prevent invalid operators at admission time.
operator: description: |- operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist. - type: string + type: string + enum: + - In + - NotIn + - Exists + - DoesNotExist
241-246: Fix minor grammar in description.Use “a service binding export”.
- not provided by an service binding export. + not provided by a service binding export.Also applies to: 343-345
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (2)
206-214: Remove duplicate SetNamespace call.Minor cleanup; SetNamespace(downstreamNS) is invoked twice.
candidate.SetUID("") candidate.SetResourceVersion("") candidate.SetNamespace(downstreamNS) candidate.SetManagedFields(nil) candidate.SetDeletionTimestamp(nil) candidate.SetDeletionGracePeriodSeconds(nil) candidate.SetOwnerReferences(nil) candidate.SetFinalizers(nil) - candidate.SetNamespace(downstreamNS) candidate.SetCreationTimestamp(v1.Time{})
236-265: Deterministic tie-breaker when both sides exist but lack owner label.Currently returns error; prefer a stable decision (e.g., earliest CreationTimestamp).
func determineOwner(providerObj, consumerObj *unstructured.Unstructured) (kubebindv1alpha2.Owner, error) { @@ - return "", fmt.Errorf("unable to determine owner") + // Both exist without owner label – choose the one created first. + if providerObj != nil && consumerObj != nil { + pt, ct := providerObj.GetCreationTimestamp(), consumerObj.GetCreationTimestamp() + if pt.Time.Before(ct.Time) { + return kubebindv1alpha2.OwnerProvider, nil + } + return kubebindv1alpha2.OwnerConsumer, nil + } + return "", fmt.Errorf("unable to determine owner") }backend/controllers/servicenamespace/servicenamespace_reconcile.go (5)
92-95: Keep “*” verbs but expand the rationale comment in codeThe permissive verbs are an intentional design choice in kube-bind due to consumer-owned provider namespaces and bidirectional flows. Please expand the inline comment so future readers don’t “tighten” it inadvertently.
- // We need list and watch for informers to be able to start. And create to create initial object. - Verbs: []string{"*"}, + // Intentional "*": informers need list/watch; initial object flows require create/update/patch/delete; + // scope is confined to consumer-owned provider namespaces per kube-bind security model. + Verbs: []string{"*"},Based on learnings
112-117: Avoid unnecessary updates to ClusterRole rulesSkip Update if rules didn’t change to reduce write churn and conflicts.
- } else { - role.Rules = permissions - if err := client.Update(ctx, role); err != nil { - return fmt.Errorf("failed to update ClusterRole %s: %w", name, err) - } - } + } else if !reflect.DeepEqual(role.Rules, permissions) { + r := role.DeepCopy() + r.Rules = permissions + if err := client.Update(ctx, r); err != nil { + return fmt.Errorf("failed to update ClusterRole %s: %w", name, err) + } + }
166-176: Avoid shadowing ‘role’ variableShadowing hampers readability; reuse the outer variable.
- if role == nil { - role := &rbacv1.Role{ + if role == nil { + role = &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: sns.Status.Namespace, }, Rules: permissions, }
178-181: Avoid unnecessary Role updatesSame rationale as for ClusterRole.
- } else { - role.Rules = permissions - if err := client.Update(ctx, role); err != nil { - return fmt.Errorf("failed to update Role %s: %w", name, err) - } - } + } else if !reflect.DeepEqual(role.Rules, permissions) { + r := role.DeepCopy() + r.Rules = permissions + if err := client.Update(ctx, r); err != nil { + return fmt.Errorf("failed to update Role %s: %w", name, err) + } + }
189-211: Avoid shadowing ‘rolebinding’; minor polishUse a new local var to avoid shadowing and improve clarity.
- if rolebinding == nil { - rolebinding := &rbacv1.RoleBinding{ + if rolebinding == nil { + rb := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: sns.Status.Namespace, }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", Namespace: sns.Namespace, Name: kuberesources.ServiceAccountName, }, }, RoleRef: rbacv1.RoleRef{ Kind: "Role", Name: name, APIGroup: "rbac.authorization.k8s.io", }, } - if err := client.Create(ctx, rolebinding); err != nil { + if err := client.Create(ctx, rb); err != nil { return fmt.Errorf("failed to create RoleBinding %s: %w", name, err) } } else {pkg/konnector/controllers/contextstore/contextstore.go (1)
89-94: Optional: cancel old context on Set to prevent leaksIf a key is overwritten without deleting, the previous context may continue running. Consider canceling the old one after unlocking.
func (c *contextStore) Set(key Key, value SyncContext) { - c.lock.Lock() - defer c.lock.Unlock() - value.key = key - c.store[key] = value + var prevCancel func() + c.lock.Lock() + if prev, ok := c.store[key]; ok { + prevCancel = prev.Cancel + } + value.key = key + c.store[key] = value + c.lock.Unlock() + if prevCancel != nil { + prevCancel() + } }backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (2)
280-289: Polish error message for empty export setMessage currently says “SchemaNotFound not found”.
- "SchemaNotFound not found", + "no exported schemas found",
309-318: Improve DifferentScopes diagnosticsMessage mixes “claimed resources” and prints a single scope/name. Report both scopes and reference “exported resources”.
- "Different scopes found: %v", - boundSchema.Spec.Scope, + "Different resource scopes found: expected %s, got %s for %s", + first, boundSchema.Spec.Scope, boundSchema.Name, @@ - return fmt.Errorf("different scopes found for claimed resources: %v", boundSchema.Name) + return fmt.Errorf("different resource scopes: expected %s, got %s for %s", first, boundSchema.Spec.Scope, boundSchema.Name)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
132-143: Enforce non-empty SelectorEnsure at least one of labelSelector or namedResource is present; both are allowed (AND semantics). CEL “size()” works on lists; use presence for objects.
type Selector struct { + // +kubebuilder:validation:XValidation:rule="has(self.labelSelector) || (has(self.namedResource) && size(self.namedResource) > 0)",message="either labelSelector or namedResource must be set" // NamedResource is a shorthand for selecting a single resource by name and namespace.
153-159: Doc nit: Namespace vs NamespacesComment refers to “Namespaces” while the field is singular and type behavior differs by isolation mode.
- // Namespace represents namespace where an object of the given group/resource may be managed. - // Namespaces matches against the metadata.namespace field. If not provided, the object is assumed to be cluster-scoped. - // Namespaces field is ignored for namespaced isolation mode. + // Namespace where the object resides. Matches metadata.namespace. + // If empty, the object is assumed cluster-scoped. + // This field is ignored in namespaced isolation mode.
180-183: Core-group string formattingGroupResource.String() prints “resource.” for core APIs. Prefer “resource.core” (consistent with ResourceGroupName elsewhere) or omit the dot.
func (r GroupResource) String() string { - return fmt.Sprintf("%s.%s", r.Resource, r.Group) + g := r.Group + if g == "" { + g = "core" + } + return fmt.Sprintf("%s.%s", r.Resource, g) }
110-115: Optional: make PermissionClaims a map-list keyed by group/resourceThis prevents duplicates at the API level and simplifies reconciliation.
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="permissionClaims are immutable" +// +listType=map +// +listMapKey=group +// +listMapKey=resource PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`Please verify controller-tools supports these markers in your current version.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (4)
90-91: Use contextstore.NewKey for consistencyPrefer NewKey(namespace, name) over manual concatenation.
- exportKey := contextstore.Key(namespace + "." + name) // Key for the export + exportKey := contextstore.NewKey(namespace, name)
145-146: Scope derivation from last schemaYou overwrite isClusterScoped on each iteration; design says all resources have uniform scope, so this is fine. Consider short-circuiting after first set or asserting uniform scope here for defense-in-depth.
181-183: Minor: generation in logYou compare against export.Generation but log schema.Generation. Log export.Generation (or both) to avoid confusion.
- logger.V(1).Info("Stopping APIServiceExport resource sync", "key", key, "reason", "GenerationChanged", "generation", schema.Generation) + logger.V(1).Info("Stopping APIServiceExport resource sync", "key", key, "reason", "GenerationChanged", "exportGeneration", export.Generation, "schemaGeneration", schema.Generation)
467-498: Set syncStore before starting goroutine to avoid race on deferred DeleteIf the goroutine exits quickly and calls Delete before Set, the entry won’t exist; then Set leaves a stale entry. Store first, then start.
- ctxWithCancel, cancel := context.WithCancel(ctx) - // Start the informers and controllers in a goroutine - go func() { - defer r.syncStore.Delete(claimKey) + ctxWithCancel, cancel := context.WithCancel(ctx) + // Store the controller context for tracking before starting goroutine + r.syncStore.Set(claimKey, contextstore.SyncContext{ + Generation: binding.Generation, + Cancel: cancel, + }) + // Start the informers and controllers in a goroutine + go func() { + defer r.syncStore.Delete(claimKey) defaultConsumerInf.Start(ctxWithCancel.Done()) @@ - // Store the controller context for tracking - r.syncStore.Set(claimKey, contextstore.SyncContext{ - Generation: binding.Generation, - Cancel: cancel, - }) + // (moved Set above)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
Makefile(3 hunks)backend/controllers/clusterbinding/clusterbinding_reconcile.go(1 hunks)backend/controllers/serviceexport/serviceexport_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(6 hunks)backend/controllers/servicenamespace/servicenamespace_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(3 hunks)backend/http/handler.go(4 hunks)backend/kubernetes/resources/namespace.go(2 hunks)backend/kubernetes/resources/namespace_test.go(1 hunks)contrib/kcp/README.md(6 hunks)contrib/kcp/deploy/bootstrap.go(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml(1 hunks)contrib/kcp/deploy/examples/cowboy.yaml(1 hunks)contrib/kcp/deploy/examples/sheriff.yaml(1 hunks)contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)pkg/indexers/serviceexport.go(2 hunks)pkg/indexers/servicenamespace.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(6 hunks)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(6 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go(2 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(3 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)pkg/resources/resources.go(1 hunks)pkg/resources/resources_test.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go(2 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)sdk/apis/kubebind/v1alpha2/boundchema_types.go(4 hunks)sdk/apis/kubebind/v1alpha2/claimable_apis.go(1 hunks)sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go(8 hunks)test/e2e/bind/happy-case_test.go(16 hunks)test/e2e/framework/clients.go(2 hunks)
🚧 Files skipped from review as they are similar to previous changes (16)
- backend/controllers/serviceexport/serviceexport_controller.go
- contrib/kcp/deploy/examples/sheriff.yaml
- contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml
- backend/controllers/clusterbinding/clusterbinding_reconcile.go
- contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml
- pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md
- pkg/indexers/servicenamespace.go
- sdk/apis/kubebind/v1alpha2/claimable_apis.go
- Makefile
- pkg/resources/resources_test.go
- pkg/indexers/serviceexport.go
- sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go
- contrib/kcp/deploy/bootstrap.go
- pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go
- backend/kubernetes/resources/namespace_test.go
- pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_controller.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamldeploy/crd/kube-bind.io_apiserviceexportrequests.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlpkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gocontrib/kcp/deploy/resources/apiexport-kube-bind.io.yamlpkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.gocontrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_controller.gobackend/http/handler.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamltest/e2e/bind/happy-case_test.godeploy/crd/kube-bind.io_apiserviceexportrequests.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlpkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.godeploy/crd/kube-bind.io_apiserviceexports.yamlpkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gocontrib/kcp/README.mdbackend/controllers/servicenamespace/servicenamespace_reconcile.gosdk/apis/kubebind/v1alpha2/apiserviceexport_types.godeploy/crd/kube-bind.io_apiservicebindings.yaml
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, there are two different ResourceGroupName() methods: BoundSchema.ResourceGroupName() for CRDs (always non-empty groups) uses simple fmt.Sprintf formatting, while APIServiceExportRequestResource.ResourceGroupName() for export requests handles empty groups by converting to "core". BoundSchema is exclusively for CRDs which cannot have empty API groups per Kubernetes validation.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gocontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlbackend/http/handler.gopkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gosdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.gocontrib/kcp/deploy/resources/apiexport-kube-bind.io.yamlsdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, BoundSchema.ResourceGroupName() is only used for CRDs (Custom Resource Definitions), and CRDs must always have non-empty API groups. Therefore, handling empty groups (core API group "") is not necessary in this context, unlike general Kubernetes GroupResource handling.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/http/handler.gopkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gocontrib/kcp/deploy/resources/apiexport-kube-bind.io.yamlsdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-22T13:32:29.499Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.499Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.gotest/e2e/bind/happy-case_test.gopkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gosdk/apis/kubebind/v1alpha2/apiserviceexport_types.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
test/e2e/bind/happy-case_test.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.gocontrib/kcp/README.mdbackend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.godeploy/crd/kube-bind.io_apiservicebindings.yaml
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts after removing them from the store. The Cancel function is extracted and called outside the lock, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/contextstore/contextstore.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts before removing them from the store. The Cancel function is called internally, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/contextstore/contextstore.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. No explicit Cancel() calls are needed when using BulkDeletePrefixed.
Applied to files:
pkg/konnector/controllers/contextstore/contextstore.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. The Delete method also cancels contexts before removal. No explicit Cancel() calls are needed when using these methods.
Applied to files:
pkg/konnector/controllers/contextstore/contextstore.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
🧬 Code graph analysis (17)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (6)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
BoundSchema(41-47)ExportedSchemas(32-32)InformerScope(59-59)sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (6)
Selector(134-142)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(162-178)APIServiceExportRequestPhaseFailed(218-218)PermissionClaim(186-193)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/third_party/conditions/util/conditions/getter.go (1)
GetMessage(94-99)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
backend/controllers/serviceexportrequest/serviceexportrequest_controller.go (1)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
BoundSchema(41-47)
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (3)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)NamedResource(145-159)GroupResource(162-178)Selector(134-142)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
InternalAPI(29-35)
pkg/resources/resources.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
Selector(134-142)NamedResource(145-159)
test/e2e/bind/happy-case_test.go (3)
test/e2e/framework/clients.go (2)
KubeClient(39-43)BindClient(57-61)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
APIServiceExportRequest(46-60)PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)pkg/konnector/controllers/cluster/serviceexport/cluster-scoped/utils.go (2)
ExtractClusterNs(97-104)Prepend(33-35)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ClusterScope(62-62)ExportedSchemas(32-32)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
APIServiceBinding(64-74)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (1)
Resource(145-145)sdk/apis/kubebind/v1alpha2/register.go (1)
Resource(42-44)
test/e2e/framework/clients.go (2)
sdk/client/informers/externalversions/kubebind/v1alpha2/interface.go (1)
Interface(26-39)sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go (1)
NewForConfig(71-79)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (8)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(40-46)Key(27-27)NewKey(33-38)SyncContext(53-57)sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
InformerScope(59-59)ClusterScope(62-62)BoundSchema(41-47)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(53-184)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-143)Resource(145-145)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(305-308)NewDynamicMultiNamespaceInformer(77-104)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (6)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(53-184)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/resources/resources.go (1)
IsClaimed(28-66)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
OwnerProvider(200-200)OwnerConsumer(202-202)Owner(196-196)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
ClusterScope(62-62)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (2)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
ConditionType(48-48)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(186-193)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
Conditions(92-92)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (8)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)pkg/resources/resources.go (1)
IsClaimed(28-66)
🪛 markdownlint-cli2 (0.18.1)
contrib/kcp/README.md
123-123: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: lint
- GitHub Check: verify
- GitHub Check: go-test
- GitHub Check: go-test-e2e
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (5)
374-419: Scope consumer list to mapped namespace; don’t abort loop on per‑item errors.Cluster‑wide List can enqueue unrelated objects. Restrict to sn.Name and continue on per‑item errors.
- objects, err := c.consumerDynamicLister.List(sel) + objects, err := c.consumerDynamicLister.ByNamespace(sn.Name).List(sel) if err != nil { runtime.HandleError(err) return } for _, obj := range objects { // Check if the object is actually claimed using the full selector logic if !c.isClaimed(obj) { continue } key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) - continue + continue } _, name, err := cache.SplitMetaNamespaceKey(key) if err != nil { runtime.HandleError(err) - continue + continue } key = fmt.Sprintf("%s/%s", sn.Status.Namespace, name) logger.V(2).Info("queueing Unstructured", "key", key, "reason", "APIServiceNamespace", "ConsumerObject", key) c.queue.Add(key) }
465-471: Avoid goroutine leak; stop chan listener on ctx.Done().Guard the loop with a select to exit when the controller stops.
- go func() { - for key := range c.namespaceCreationNotifyChan { - logger.Info("received namespace creation notification", "namespace", key) - c.enqueueConsumerByKey(logger, key) - } - }() + go func() { + for { + select { + case <-ctx.Done(): + return + case key, ok := <-c.namespaceCreationNotifyChan: + if !ok { + return + } + logger.Info("received namespace creation notification", "namespace", key) + c.enqueueConsumerByKey(logger, key) + } + } + }()
453-476: Wait for caches to sync before starting workers.Reduce flakiness by waiting for informer caches.
logger.Info("Starting controller") defer logger.Info("Shutting down controller") c.serviceNamespaceInformer.Informer().AddDynamicEventHandler(ctx, controllerName, cache.ResourceEventHandlerFuncs{ @@ }) + // Wait for caches to sync + cache.WaitForCacheSync(ctx.Done(), c.serviceNamespaceInformer.Informer().HasSynced) + // Provider multi-namespace informer exposes WaitForCacheSync; ignore map result but block until synced. + c.providerDynamicInformer.WaitForCacheSync(ctx.Done()) + // Start a goroutine to listen for namespace creation notifications. go func() {
125-131: Defensive: guard type assertion in getProviderObject.Avoid panic if the informer returns a non‑unstructured implementation.
- return obj.(*unstructured.Unstructured), nil + u, ok := obj.(*unstructured.Unstructured) + if !ok { + return nil, fmt.Errorf("unexpected provider object type %T", obj) + } + return u, nil
193-201: Remove unused fieldsproviderBindClientandapiServiceExportfrom ClaimedresourcesReconciler
Both fields are injected in the constructor but never accessed in any reconciler methods—drop them and clean up the constructor signature.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
backend/controllers/servicenamespace/servicenamespace_reconcile.go(3 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/resources/resources.go(1 hunks)test/e2e/bind/happy-case_test.go(16 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-22T13:32:29.499Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.499Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
pkg/resources/resources.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.gotest/e2e/bind/happy-case_test.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
backend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.gotest/e2e/bind/happy-case_test.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
backend/controllers/servicenamespace/servicenamespace_reconcile.gotest/e2e/bind/happy-case_test.go
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
🧬 Code graph analysis (4)
pkg/resources/resources.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
Selector(134-142)NamedResource(145-159)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
ClusterScope(62-62)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (6)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)pkg/resources/resources.go (1)
IsClaimed(28-69)
test/e2e/bind/happy-case_test.go (5)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)NamespacedScope(63-63)InformerScope(59-59)test/e2e/framework/clients.go (2)
KubeClient(39-43)BindClient(57-61)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
APIServiceExportRequest(46-60)PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)pkg/konnector/controllers/cluster/serviceexport/cluster-scoped/utils.go (2)
ExtractClusterNs(97-104)Prepend(33-35)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespaceList(64-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: verify
- GitHub Check: go-test
- GitHub Check: go-test-e2e
- GitHub Check: lint
🔇 Additional comments (3)
pkg/resources/resources.go (1)
55-65: Fix wildcard and cluster-scope handling in named-resource matching.
NamedResource.Namespaceis still treated literally, so a selector using"*"never matches any namespace, and leaving the namespace blank incorrectly matches namespaced objects (it should only match cluster-scoped resources). Both behaviors break the documented contract onNamedResource.Apply this diff to honor the wildcard and enforce the cluster-scope default:
- for _, nr := range selector.NamedResource { - if nr.Namespace != "" && nr.Namespace != obj.GetNamespace() { - continue - } + for _, nr := range selector.NamedResource { + ns := obj.GetNamespace() + switch { + case nr.Namespace == "*": + // wildcard matches any namespace + case nr.Namespace != "": + if nr.Namespace != ns { + continue + } + default: + // empty namespace means cluster-scoped + if ns != "" { + continue + } + } if nr.Name == obj.GetName() { namedResourceMatches = true break } }test/e2e/bind/happy-case_test.go (2)
347-368: Bug: discover APIServiceNamespace in clusterNs; fix brittle unset check.For cluster-scoped resources with Cluster informerScope, list APIServiceNamespaces in clusterNs (not providerNS) and check clusterNs is set. Otherwise providerNS may be empty and subsequent typed client calls fail.
- if withPermissionClaims && - informerScope == kubebindv1alpha2.ClusterScope && - resourceScope == apiextensionsv1.ClusterScoped { - if providerNS == "unknown" { - t.Fatal("providerNS is not set. Programming error in the test.") - } + if withPermissionClaims && + informerScope == kubebindv1alpha2.ClusterScope && + resourceScope == apiextensionsv1.ClusterScoped { + if clusterNs == "" || clusterNs == "unknown" { + t.Fatal("clusterNs is not set. Programming error in the test.") + } var namespaces *kubebindv1alpha2.APIServiceNamespaceList t.Logf("Waiting for APIServiceNamespace to be created on provider side") require.Eventually(t, func() bool { var err error - namespaces, err = providerBindClient.KubeBindV1alpha2().APIServiceNamespaces(providerNS).List(ctx, metav1.ListOptions{}) + namespaces, err = providerBindClient.KubeBindV1alpha2().APIServiceNamespaces(clusterNs).List(ctx, metav1.ListOptions{}) if err != nil { return false } return len(namespaces.Items) == 1 && namespaces.Items[0].Status.Namespace != "" }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for APIServiceNamespace to be created on provider side") providerNS = namespaces.Items[0].Status.Namespace require.NotEmpty(t, providerNS, "No cluster namespaces found") }
141-192: Confirm E2E test intent for provider-controlled selectors. I couldn't locate whereAPIServiceExportRequest.Spec.PermissionClaims(includingLabelSelector) is consumed or overridden in the backend; ensure your test’s injected selectors are applied by the reconciler as intended or adjust the test accordingly.
6d15d6f to
5164358
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
♻️ Duplicate comments (6)
contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (1)
220-324: Add missing validations and list annotations for permissionClaims.The permissionClaims schema is missing essential validations that were identified in previous reviews:
- List deduplication: Add
x-kubernetes-list-type: mapwithx-kubernetes-list-map-keys: [group, resource]to prevent duplicate claims- Selector validation: Require at least one of
labelSelectorornamedResourceto be set- NamedResource deduplication: Add list map annotations for
namedResourcearray- AND semantics: Clarify that multiple sub-selectors are intersected
Apply the validations as suggested in the previous review to ensure schema correctness and prevent invalid configurations.
deploy/crd/kube-bind.io_apiserviceexports.yaml (1)
493-597: Add missing validations to keep CRD and APIResourceSchema in sync.The
permissionClaimsschema in this CRD is missing the same essential validations that were identified in previous reviews and in the APIResourceSchema file:
- List deduplication: Add
x-kubernetes-list-type: mapwithx-kubernetes-list-map-keys: [group, resource]- Selector validation: Require at least one of
labelSelectorornamedResourceto be set- NamedResource deduplication: Add list map annotations for
namedResourcearray- AND semantics: Clarify that multiple sub-selectors are intersected
Keeping the CRD and APIResourceSchema schemas consistent prevents client/server validation drift.
Apply the same validations as suggested for the APIResourceSchema to maintain consistency.
deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (1)
224-328: [Duplicate] Enforce uniqueness for permissionClaims to prevent duplicate group/resource pairs.The permissionClaims array should use
x-kubernetes-list-type: mapwithx-kubernetes-list-map-keys: [group, resource]to prevent duplicate entries for the same GroupResource, as noted in a previous review.Without this constraint, API clients could create multiple claims for the same group/resource (e.g., via merges or patches), leading to ambiguous permission semantics.
Apply this diff:
permissionClaims: items: type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource x-kubernetes-validations: - message: permissionClaims are immutable rule: self == oldSelfpkg/resources/resources.go (1)
55-66: [Duplicate] Implement wildcard namespace matching for NamedResource.The current logic at line 58 treats
"*"as a literal namespace name rather than a wildcard. A NamedResource withNamespace: "*"will never match any object because it compares"*"against the object's actual namespace.Apply this diff to implement wildcard matching:
if len(selector.NamedResource) > 0 { namedResourceMatches = false // Default to false, must match at least one for _, nr := range selector.NamedResource { - if nr.Namespace != "" && nr.Namespace != obj.GetNamespace() { + if nr.Namespace != "" && nr.Namespace != "*" && nr.Namespace != obj.GetNamespace() { continue } if nr.Name == obj.GetName() { namedResourceMatches = true break } } }This ensures that
Namespace: "*"matches any namespace,Namespace: ""matches cluster-scoped resources, and specific namespace names match exactly.pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
191-204: Fail closed on invalid label selector; precompute to avoid repeat parsing.The current implementation falls back to an unfiltered informer when label selector conversion fails, which can over-scope watches and create a security issue. Precompute the selector string once before the factory creation and fail early if conversion fails.
Based on past review comments.
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (1)
138-142: Defer BoundSchema creation for resources with empty versions.Skipping BoundSchema creation when
res.Versionsis empty can cause downstream "BoundSchemaNotFound" errors, as the spec allows empty versions (provider selects). However, per maintainer comments, lifecycle management for BoundSchemas is tracked separately in issue #297.Based on past review comments and maintainer clarification.
🧹 Nitpick comments (2)
contrib/kcp/README.md (1)
123-125: Add language identifier to fenced code block.The fenced code block should specify a language identifier (e.g.,
bash) to satisfy markdown linting rules and improve syntax highlighting.Apply this diff:
-``` +```bash kubectl apply -f contrib/kcp/deploy/examples/cowboy.yaml kubectl apply -f contrib/kcp/deploy/examples/sheriff.yaml</blockquote></details> <details> <summary>sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)</summary><blockquote> `110-114`: **Consider using list map for immutability enforcement.** The immutability constraint (`self == oldSelf`) is present, but for better structural enforcement of list immutability, consider using kubebuilder's `listMapKey` validation to prevent reordering or partial updates while maintaining immutability semantics. Apply this enhancement if stronger list immutability guarantees are desired: ```diff +// +kubebuilder:validation:XValidation:rule="self.all(x, self.exists(y, x.group == y.group && x.resource == y.resource))",message="permissionClaims cannot be reordered" +// +listMapKey=resource +// +listMapKey=group PermissionClaims []PermissionClaim `json:"permissionClaims,omitempty"`Based on past review comments.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
Makefile(3 hunks)backend/controllers/clusterbinding/clusterbinding_reconcile.go(1 hunks)backend/controllers/serviceexport/serviceexport_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(6 hunks)backend/controllers/servicenamespace/servicenamespace_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(3 hunks)backend/http/handler.go(4 hunks)backend/kubernetes/resources/namespace.go(2 hunks)backend/kubernetes/resources/namespace_test.go(1 hunks)contrib/kcp/README.md(6 hunks)contrib/kcp/deploy/bootstrap.go(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml(1 hunks)contrib/kcp/deploy/examples/cowboy.yaml(1 hunks)contrib/kcp/deploy/examples/sheriff.yaml(1 hunks)contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)pkg/indexers/serviceexport.go(2 hunks)pkg/indexers/servicenamespace.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(6 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go(2 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(3 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)pkg/resources/resources.go(1 hunks)pkg/resources/resources_test.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go(2 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)sdk/apis/kubebind/v1alpha2/boundchema_types.go(4 hunks)sdk/apis/kubebind/v1alpha2/claimable_apis.go(1 hunks)sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go(8 hunks)test/e2e/bind/happy-case_test.go(16 hunks)test/e2e/framework/clients.go(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md
🚧 Files skipped from review as they are similar to previous changes (14)
- backend/controllers/serviceexport/serviceexport_controller.go
- pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go
- contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml
- test/e2e/framework/clients.go
- contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml
- contrib/kcp/deploy/examples/cowboy.yaml
- Makefile
- pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go
- pkg/resources/resources_test.go
- pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go
- backend/controllers/clusterbinding/clusterbinding_reconcile.go
- pkg/indexers/serviceexport.go
- backend/controllers/serviceexportrequest/serviceexportrequest_controller.go
- pkg/konnector/controllers/contextstore/contextstore.go
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
contrib/kcp/deploy/bootstrap.gobackend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlcontrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yamlcontrib/kcp/deploy/resources/apiexport-kube-bind.io.yamlbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-22T13:32:29.499Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.499Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
contrib/kcp/README.mdpkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.godeploy/crd/kube-bind.io_apiserviceexportrequests.yaml
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, there are two different ResourceGroupName() methods: BoundSchema.ResourceGroupName() for CRDs (always non-empty groups) uses simple fmt.Sprintf formatting, while APIServiceExportRequestResource.ResourceGroupName() for export requests handles empty groups by converting to "core". BoundSchema is exclusively for CRDs which cannot have empty API groups per Kubernetes validation.
Applied to files:
sdk/apis/kubebind/v1alpha2/boundchema_types.gobackend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, BoundSchema.ResourceGroupName() is only used for CRDs (Custom Resource Definitions), and CRDs must always have non-empty API groups. Therefore, handling empty groups (core API group "") is not necessary in this context, unlike general Kubernetes GroupResource handling.
Applied to files:
sdk/apis/kubebind/v1alpha2/boundchema_types.gobackend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
sdk/apis/kubebind/v1alpha2/boundchema_types.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. No explicit Cancel() calls are needed when using BulkDeletePrefixed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. The Delete method also cancels contexts before removal. No explicit Cancel() calls are needed when using these methods.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts after removing them from the store. The Cancel function is extracted and called outside the lock, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts before removing them from the store. The Cancel function is called internally, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
🧬 Code graph analysis (18)
contrib/kcp/deploy/bootstrap.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(186-193)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (2)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
ConditionType(48-48)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(186-193)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
sdk/apis/kubebind/v1alpha2/register.go (1)
Resource(42-44)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (8)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)pkg/resources/resources.go (1)
IsClaimed(28-69)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
ClusterScope(62-62)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
Conditions(92-92)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (6)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(40-46)Key(27-27)NewKey(33-38)SyncContext(53-57)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(53-184)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(305-308)NewDynamicMultiNamespaceInformer(77-104)
pkg/indexers/servicenamespace.go (1)
sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)
pkg/resources/resources.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
Selector(134-142)NamedResource(145-159)
backend/kubernetes/resources/namespace_test.go (1)
backend/kubernetes/resources/namespace.go (2)
IdentityAnnotationKey(31-31)CreateNamespace(56-88)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ClusterScope(62-62)ExportedSchemas(32-32)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (6)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(53-184)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/resources/resources.go (1)
IsClaimed(28-69)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)
sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
PermissionClaim(186-193)
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (3)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)NamedResource(145-159)GroupResource(162-178)Selector(134-142)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
InternalAPI(29-35)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (6)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
BoundSchema(41-47)ExportedSchemas(32-32)sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
Selector(134-142)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(162-178)PermissionClaim(186-193)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/third_party/conditions/util/conditions/getter.go (1)
GetMessage(94-99)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (5)
pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/indexers/servicenamespace.go (2)
ServiceNamespaceByNamespace(26-26)IndexServiceNamespaceByNamespace(29-36)pkg/konnector/controllers/contextstore/contextstore.go (1)
New(59-63)pkg/indexers/serviceexport.go (2)
ServiceExportByBoundSchema(27-27)IndexServiceExportByBoundSchema(54-65)
🪛 markdownlint-cli2 (0.18.1)
contrib/kcp/README.md
123-123: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: Generate and push docs
- GitHub Check: verify
- GitHub Check: go-test-e2e
- GitHub Check: lint
- GitHub Check: go-test
🔇 Additional comments (40)
contrib/kcp/deploy/examples/sheriff.yaml (1)
5-6: Intent metadata looks consistentThe new
spec.intentaddition keeps the manifest well-formed and aligns with the metadata pattern in the other example resources.contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml (1)
5-5: LGTM: APIResourceSchema version identifier updated.The metadata name update from v250809-5ed76a1 to v250929-62fc678 follows the version-stamped naming convention for APIResourceSchemas.
sdk/apis/kubebind/v1alpha2/boundchema_types.go (4)
30-32: LGTM: ExportedSchemas type well-defined.The new
ExportedSchemasmap type provides efficient schema lookup byresource.groupkey. The comment accurately clarifies that version is not part of the key, preventing confusion with callers.
21-21: LGTM: fmt import added for string formatting.The
fmtimport is necessary for theResourceGroupName()method implementation.
49-54: LGTM: ResourceGroupName() correctly implements CRD naming.The method correctly formats the resource group name as
"plural.group"for CRD resources. The cross-reference comment toAPIServiceExportRequestResourceis helpful for maintaining consistency across the codebase.Based on learnings: BoundSchema is exclusively for CRDs, which always have non-empty groups, so no special handling for empty groups is needed here.
261-267: LGTM: Receiver naming improved for consistency.The receiver name changes from
intobimprove consistency with otherBoundSchemamethods (e.g.,ResourceGroupName()). This is a good stylistic improvement with no functional impact.backend/http/handler.go (3)
48-48: LGTM: helpers import added for schema conversion.The helpers package import provides the
UnstructuredToBoundSchemafunction needed for typed schema conversion.
379-418: LGTM: Typed schema handling with proper version selection.The migration to typed
ExportedSchemasimproves type safety and maintainability. The version selection logic correctly:
- Validates that versions exist before accessing
- Picks the first served version instead of blindly using index 0
- Skips schemas with no served versions with appropriate logging
This prevents UI rendering of invalid or unserved schema versions.
546-576: LGTM: Efficient typed schema conversion and lookup.The refactored
getBackendDynamicResourcecorrectly:
- Lists dynamic resources as before
- Converts each unstructured item to a typed
BoundSchemavia helpers- Keys the map by
ResourceGroupName()for efficient lookup- Propagates conversion errors appropriately
This provides better type safety and efficient schema access for the UI layer.
contrib/kcp/README.md (3)
26-55: LGTM: Documentation updated for new deployment flow.The documentation correctly adds:
- Explicit Dex startup instructions for clarity
- New
--consumer-scope=clusterflag reflecting permission-claims scope requirements- Updated
--schema-source apiresourceschemasaligning with typed schema handlingThese updates improve deployment clarity and align with the PR's permission-claims feature.
82-125: LGTM: Paths and kubeconfig handling updated correctly.The documentation correctly updates:
- CRD and APIResourceSchema paths to
contrib/kcp/deploy/examples/- Kubeconfig secret extraction to match new secret naming pattern
- Cluster identifiers and URLs throughout
- Object creation to include both
cowboy.yamlandsheriff.yamlThese changes align with the PR's restructured deployment artifacts.
129-152: LGTM: Debug section enhanced with claimed resource examples.The debug section additions provide helpful examples for testing claimed resources (ConfigMaps, Secrets) with proper labels and namespace handling. The formatting improvements enhance readability.
deploy/crd/kube-bind.io_apiserviceexports.yaml (1)
689-690: LGTM: v1alpha2 promoted to storage version.The change to make v1alpha2 the storage version (
storage: true) follows standard Kubernetes API graduation patterns. This aligns with the broader API evolution in this PR.backend/kubernetes/resources/namespace.go (1)
35-54: Legacy annotation migration looks solid.The patch-based migration plus dual-key acceptance keeps upgrades safe while cleaning up legacy state.
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
58-58: Document or validate the apiServiceExport parameter.The new
apiServiceExportparameter is added to establish owner references for consumer-side resource creation. However, there's no nil-check or documentation indicating whethernilis a valid value.Please clarify: is
nila valid value for this parameter? If not, add a validation guard at the constructor entry:func NewController( apiServiceExport *kubebindv1alpha2.APIServiceExport, // used to establish owner references when create happens from the consumer side. gvr schema.GroupVersionResource, providerNamespace string, providerNamespaceUID string, consumerConfig, providerConfig *rest.Config, consumerDynamicInformer informers.GenericInformer, providerDynamicInformer multinsinformer.GetterInformer, serviceNamespaceInformer dynamic.Informer[bindlisters.APIServiceNamespaceLister], ) (*controller, error) { + if apiServiceExport == nil { + return nil, fmt.Errorf("apiServiceExport must not be nil") + } queue := workqueue.NewTypedRateLimitingQueueWithConfig(workqueue.DefaultTypedControllerRateLimiter[string](), workqueue.TypedRateLimitingQueueConfig[string]{Name: controllerName})Based on learnings: In similar controllers, the lifecycle ensures non-nil when active, but constructors should validate inputs.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (3)
80-82: LGTM! Proper indexer registration for ServiceNamespaceByNamespace.The addition of the
ServiceNamespaceByNamespaceindexer for the dynamic service namespace informer enables efficient lookup of service namespaces by their associated namespace, which is essential for the permission claims flow.
124-125: LGTM! Correct indexer for BoundSchema-based lookups.The switch to
ServiceExportByBoundSchemaindexer addresses the previous issue where exports were indexed by name rather than by the CRDs they reference. This enables proper multi-resource export handling.
187-208: LGTM! Improved CRD event handling with correct index.The updated
enqueueCRDimplementation correctly uses theServiceExportByBoundSchemaindex to find all exports that reference a given CRD, and properly handles the indexer's error contract (no NotFound errors).backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
79-227: LGTM! Comprehensive per-export RBAC provisioning with proper tenant isolation.The implementation correctly:
- Lists APIServiceExports in the namespace and provisions RBAC per export
- Includes
sns.Namein RBAC resource names (kube-binder-<sns>-export-<export>) to prevent cross-tenant collisions- Handles both cluster-scoped and namespace-scoped RBAC depending on
c.scope- Implements update paths for Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings
- Uses
"*"verbs intentionally (per learning: scoped to consumer-owned provider namespaces, supports bidirectional flow)Based on learnings: The wildcard verb usage is a deliberate design decision in kube-bind's security model, and stale RBAC cleanup is tracked in issue #329.
274-316: LGTM! Helper methods correctly implement cache access for RBAC resources.The helper methods (
getPermissionClaimsClusterRole,getPermissionClaimsRole, etc.) properly constructtypes.NamespacedNamekeys and use the controller-runtime cache interface to retrieve RBAC objects. The error handling correctly propagatesIsNotFounderrors to callers.contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml (1)
52-52: LGTM! Schema version updates align with new PermissionClaims feature.The schema version bumps for
apiservicebindings,apiserviceexportrequests, andapiserviceexportscorrectly reference the updated schemas (v250929-62fc678) that include the new permissionClaims field.Also applies to: 57-57, 62-62
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
26-26: LGTM: labelSelector field and constructor addition.The addition of the
labelSelectorfield and the corresponding constructor parameter is clean and follows the established pattern for dynamic informer configuration.Also applies to: 64-64, 82-82, 94-94
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
132-159: LGTM: Selector and NamedResource types are well-defined.The
SelectorandNamedResourcetypes are clearly documented with appropriate validation constraints. The structure supports both named resource selection and label-based selection patterns.
180-182: LGTM: GroupResource.String() method.The
String()method provides a clean, readable representation for GroupResource identifiers.
184-193: LGTM: PermissionClaim type definition.The
PermissionClaimtype clearly embedsGroupResourceand requires aSelector, ensuring claims are properly scoped. The documentation and validation are appropriate.
195-207: LGTM: Owner type and constants.The
Ownertype and constants (OwnerProvider,OwnerConsumer) provide clear ownership semantics for resource management. The implementation is clean and consistent.pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (8)
22-23: LGTM: Import and signature updates for centralized context store.The addition of
maps,slices,contextstore, and permission claim controller imports, along with the updated signatures to includenamespace, are clean and consistent. The centralized context store pattern improves lifecycle management.Also applies to: 39-40, 44-44, 60-60, 69-69, 88-90
94-98: LGTM: Context cleanup using BulkDeletePrefixed.The cleanup logic correctly uses
BulkDeletePrefixedto stop all controllers when the export or binding is removed. The centralized store handles context cancellation automatically, and the logging provides clear shutdown reasons.Based on learnings.
Also applies to: 109-114
119-119: LGTM: Schema processing and cleanup logic.The schema processing correctly tracks processed schemas and determines cluster scope from any schema (valid per design constraint that all claims have the same scope). The cleanup logic with prefix filtering properly distinguishes between schema and claim controllers.
Based on learnings.
Also applies to: 128-165
171-298: LGTM: Schema controller lifecycle with centralized store.The controller lifecycle correctly tracks generation, handles cleanup on generation changes, and stores context with proper generation metadata. Passing
nilforlabelSelectoris appropriate for schema controllers (filtering is reserved for permission claims). The owner reference passing enables proper ServiceNamespace creation.
300-352: LGTM: Permission claims orchestration.The permission claims controller lifecycle correctly tracks each claim independently with unique keys, validates claimability, checks generation per claim, and cleans up removed claims. The "claim" prefix ensures proper separation from schema controllers.
390-433: LGTM: Well-structured informer setup with clear documentation.The 4-scenario logic (A1, A2, B1, B2) for cluster/namespace-scoped resources with/without label selectors is clearly documented and correctly implemented. The explanatory comment (lines 390-403) is valuable for maintainability.
435-501: LGTM: Controller startup with proper lifecycle management.The controller creation and startup sequence is correct, with proper owner reference passing, defer-based cleanup, and critical ordering (namespace controller before claimed resources). The generation tracking uses
binding.Generationappropriately for claim lifecycle.
564-564: LGTM: Updated condition terminology.The condition messages correctly use "BoundSchemas" terminology, consistent with the new types introduced in this PR.
Also applies to: 578-580
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (6)
47-47: LGTM: Refactored reconcile flow with validation.The reconcile flow correctly sequences bound schema provisioning, validation, and export creation, with clear error wrapping at each stage. The shift to client-based retrieval (line 47) is intentional and documented.
Also applies to: 56-80
82-130: LGTM: Dynamic exported schema retrieval.The
getExportedSchemasfunction correctly uses the client to list exported schemas with label selectors, converts them to BoundSchema objects, and indexes by ResourceGroupName. The comment explains the client-based approach rationale.
241-241: LGTM: Permission claims propagated to export.Line 241 correctly propagates
req.Spec.PermissionClaimstoexport.Spec.PermissionClaims, enabling the export to carry the permission claims to downstream consumers.
268-319: LGTM: Validation logic for schemas and scope uniformity.The validation correctly checks for exported schema presence and enforces uniform scope across all requested resources. The implementation is sound.
321-356: LGTM: Permission claims validation.The validation correctly checks that all permission claims are for claimable APIs and prevents duplicate group/resource combinations. Error handling sets appropriate failure conditions.
361-369: LGTM: Claimability check helper.The
isClaimableAPIfunction correctly validates claims against the knownClaimableAPIslist, comparing group and plural resource names.
Signed-off-by: Mangirdas Judeikis <mangirdas@judeikis.lt> On-behalf-of: @SAP mangirdas.judeikis@sap.com
5164358 to
84bf4a9
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (8)
pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
192-202: Precompute label selector at construction time.The label selector is converted to a Kubernetes selector on every informer start. If conversion fails, the code falls back to an unfiltered watch, which can over-scope permissions. Based on the past review comment, this should be precomputed at construction time to fail early and avoid repeated parsing.
Apply this fix in the constructor:
func NewDynamicMultiNamespaceInformer( gvr schema.GroupVersionResource, providerNamespace string, providerConfig *rest.Config, serviceNamespaceInformer dynamic.Informer[bindlisters.APIServiceNamespaceLister], labelSelector *metav1.LabelSelector, ) (*DynamicMultiNamespaceInformer, error) { providerConfig = rest.CopyConfig(providerConfig) providerConfig = rest.AddUserAgent(providerConfig, controllerName) providerDynamicClient, err := dynamicclient.NewForConfig(providerConfig) if err != nil { return nil, err } + + var selectorString string + if labelSelector != nil { + sel, err := metav1.LabelSelectorAsSelector(labelSelector) + if err != nil { + return nil, fmt.Errorf("invalid label selector: %w", err) + } + selectorString = sel.String() + } inf := DynamicMultiNamespaceInformer{ gvr: gvr, - labelSelector: labelSelector, + labelSelector: labelSelector, + selectorString: selectorString, providerNamespace: providerNamespace, providerDynamicClient: providerDynamicClient, serviceNamespaceInformer: serviceNamespaceInformer, namespaceInformers: map[string]informers.GenericInformer{}, namespaceCancel: map[string]func(){}, } return &inf, nil }Then update the informer struct to include the precomputed string:
type DynamicMultiNamespaceInformer struct { gvr schema.GroupVersionResource labelSelector *metav1.LabelSelector + selectorString string providerNamespace stringAnd use it in enqueueServiceNamespace:
var tweakListOptions func(options *metav1.ListOptions) - if inf.labelSelector != nil { + if inf.selectorString != "" { tweakListOptions = func(options *metav1.ListOptions) { - selector, err := metav1.LabelSelectorAsSelector(inf.labelSelector) - if err != nil { - utilruntime.HandleError(fmt.Errorf("failed to convert label selector: %w", err)) - return - } - options.LabelSelector = selector.String() + options.LabelSelector = inf.selectorString } }deploy/crd/kube-bind.io_apiserviceexportrequests.yaml (1)
224-329: Prevent duplicate permissionClaims; encode as map keyed by group/resourceMake permissionClaims a map list to avoid dupes and patch-merge surprises. Optional: mark namedResource as atomic.
permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: @@ - type: array + type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource x-kubernetes-validations: - message: permissionClaims are immutable rule: self == oldSelfOptionally make namedResource atomic:
namedResource: description: NamedResource is a shorthand for selecting a single resource by name and namespace. items: @@ type: object - type: array + type: array + x-kubernetes-list-type: atomicpkg/resources/resources.go (1)
55-66: Fix NamedResource namespace semantics: support "*" and avoid cross-namespace matches
- Empty namespace should match only cluster-scoped objects.
- "*" should match any namespace.
- Current logic can over-grant by matching names across namespaces.
- for _, nr := range selector.NamedResource { - if nr.Namespace != "" && nr.Namespace != obj.GetNamespace() { - continue - } - if nr.Name == obj.GetName() { - namedResourceMatches = true - break - } - } + for _, nr := range selector.NamedResource { + // Empty namespace -> match only cluster-scoped objects + if nr.Namespace == "" { + if obj.GetNamespace() != "" { + continue + } + } else if nr.Namespace != "*" && nr.Namespace != obj.GetNamespace() { + // "*" matches any namespace + continue + } + if nr.Name == obj.GetName() { + namedResourceMatches = true + break + } + }Optional: avoid shadowing the selector param at Line 43 by renaming local var, e.g., ls := metav1.LabelSelectorAsSelector(...).
deploy/crd/kube-bind.io_apiserviceexports.yaml (1)
493-597: Add list map-keys and selector presence validation for permissionClaims
- Deduplicate entries with list map-keys.
- Enforce that at least one of labelSelector or a non-empty namedResource is set.
- Make namedResource a map list keyed by name/namespace.
permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource items: @@ properties: selector: description: Selector is a resource selector that selects objects of a GVR. properties: labelSelector: @@ x-kubernetes-map-type: atomic namedResource: description: NamedResource is a shorthand for selecting a single resource by name and namespace. items: @@ required: - name type: object - type: array + type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - name + - namespace + x-kubernetes-validations: + - message: at least one of labelSelector or namedResource must be set + rule: has(self.labelSelector) || (has(self.namedResource) && size(self.namedResource) > 0) type: object required: - resource - selector type: objectPlease mirror the same rules in the APIResourceSchema to keep CRD and schema consistent.
test/e2e/bind/happy-case_test.go (1)
348-373: Bug: wrong namespace used for APIServiceNamespace lookup; fix unset check tooFor Cluster informerScope + cluster‑scoped services, list APIServiceNamespaces in clusterNs (not providerNS). Also guard on clusterNs, not providerNS.
- if withPermissionClaims && - informerScope == kubebindv1alpha2.ClusterScope && - resourceScope == apiextensionsv1.ClusterScoped { - if providerNS == "unknown" { - t.Fatal("providerNS is not set. Programming error in the test.") - } + if withPermissionClaims && + informerScope == kubebindv1alpha2.ClusterScope && + resourceScope == apiextensionsv1.ClusterScoped { + if clusterNs == "" || clusterNs == "unknown" { + t.Fatal("clusterNs is not set. Programming error in the test.") + } var namespaces *kubebindv1alpha2.APIServiceNamespaceList t.Logf("Waiting for APIServiceNamespace to be created on provider side") require.Eventually(t, func() bool { var err error - namespaces, err = providerBindClient.KubeBindV1alpha2().APIServiceNamespaces(providerNS).List(ctx, metav1.ListOptions{}) + namespaces, err = providerBindClient.KubeBindV1alpha2().APIServiceNamespaces(clusterNs).List(ctx, metav1.ListOptions{}) if err != nil { return false } return len(namespaces.Items) == 1 && namespaces.Items[0].Status.Namespace != "" }, wait.ForeverTestTimeout, time.Millisecond*100, "waiting for APIServiceNamespace to be created on provider side") providerNS = namespaces.Items[0].Status.Namespace require.NotEmpty(t, providerNS, "No cluster namespaces found") }pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (2)
136-144: Preserve resourceVersion on consumer Update to avoid conflictscandidate has empty resourceVersion; Update will fail. Copy current RV before updating.
- if !equality.Semantic.DeepEqual(candidate, current) { - logger.Info("Updating consumer object data", "consumerNamespace", consumerNS, "consumerName", consumerObj.GetName()) - if _, err := r.updateConsumerObject(ctx, candidate); err != nil { + if !equality.Semantic.DeepEqual(candidate, current) { + logger.Info("Updating consumer object data", "consumerNamespace", consumerNS, "consumerName", consumerObj.GetName()) + // set resourceVersion for optimistic concurrency + candidate.SetResourceVersion(consumerObj.GetResourceVersion()) + if _, err := r.updateConsumerObject(ctx, candidate); err != nil { logger.Error(err, "error updating consumer object") return err } }
173-177: Avoid shadowing and preserve provider resourceVersion on UpdateDon’t shadow providerObj; keep current RV and set it on candidate before update.
- providerObj := candidateFromOwnerObj(providerNamespace, providerObj) - if !equality.Semantic.DeepEqual(providerObj, candidate) { - logger.Info("updating consumer owned object at provider") - return r.updateProviderObject(ctx, candidate) - } + providerRV := providerObj.GetResourceVersion() + sanitizedProvider := candidateFromOwnerObj(providerNamespace, providerObj) + if !equality.Semantic.DeepEqual(sanitizedProvider, candidate) { + candidate.SetResourceVersion(providerRV) + logger.Info("updating consumer owned object at provider") + return r.updateProviderObject(ctx, candidate) + }backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (1)
139-142: Bug: skipping BoundSchema creation when versions omitted.The code skips processing resources with empty Versions (line 140-142), but the API spec allows empty Versions (line 122 in apiserviceexportrequest_types.go shows Versions is optional). This will cause validation failures at line 294-303 when the schema isn't found.
Remove the Versions check:
// Ensure all bound schemas exist for _, res := range req.Spec.Resources { - if len(res.Versions) == 0 { - continue - } - for _, boundSchema := range exportedSchemas {
🧹 Nitpick comments (15)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (1)
157-159: Move permission claims reference outside the loop.
referencePermissionClaimsis called once per schema, but it performs the same idempotent assignment each time (binding.Status.PermissionClaims = export.Spec.PermissionClaims). This is inefficient—with N schemas, the same slice is assigned N times. Additionally, if there are zero schemas, permission claims are never referenced, creating inconsistent behavior.Move the call outside the loop. Recommend placing it after line 149 (after successfully fetching schemas, before the loop):
// Get all BoundSchema objects referenced by the export schemas, err := r.getSchemasFromExport(ctx, export) if err != nil { conditions.MarkFalse( binding, kubebindv1alpha2.APIServiceBindingConditionConnected, "BoundSchemaFetchFailed", conditionsapi.ConditionSeverityError, "Failed to fetch BoundSchema objects: %s", err, ) // We dont have schema - try again. Might be a race on provider side. return err } + + if err := r.referencePermissionClaims(ctx, binding, export); err != nil { + errs = append(errs, err) + } // Process each schema for _, schema := range schemas { if err := r.referenceBoundSchema(ctx, binding, schema.Name); err != nil { errs = append(errs, err) } - if err := r.referencePermissionClaims(ctx, binding, export); err != nil { - errs = append(errs, err) - } - if err := r.ensureCRDsFromBoundSchema(ctx, binding, schema); err != nil { errs = append(errs, err) } }pkg/indexers/serviceexport.go (1)
40-65: Refactor to eliminate code duplication.The two indexer functions contain identical logic for extracting BoundSchema names. Consider having the error-returning wrapper delegate to the controller-runtime function to avoid duplication.
Apply this refactor:
-// IndexServiceExportByBoundSchema is a controller-runtime compatible indexer function. -func IndexServiceExportByBoundSchema(obj any) ([]string, error) { - export, ok := obj.(*v1alpha2.APIServiceExport) - if !ok { - return nil, nil - } - - names := make([]string, 0, len(export.Spec.Resources)) - for _, res := range export.Spec.Resources { - names = append(names, res.ResourceGroupName()) - } - return names, nil -} +// IndexServiceExportByBoundSchema wraps the controller-runtime indexer with an error return. +func IndexServiceExportByBoundSchema(obj any) ([]string, error) { + if cObj, ok := obj.(client.Object); ok { + return IndexServiceExportByBoundSchemaControllerRuntime(cObj), nil + } + return nil, nil +}pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (1)
54-56: Good guard; also handle APIServiceNamespace create races gracefully
- Returning an error when apiServiceExport is nil is correct.
- Suggest handling AlreadyExists on APIServiceNamespace create to avoid transient failures on cache staleness.
- sn, err = r.createServiceNamespace(ctx, &kubebindv1alpha2.APIServiceNamespace{ + sn, err = r.createServiceNamespace(ctx, &kubebindv1alpha2.APIServiceNamespace{ ObjectMeta: metav1.ObjectMeta{ Name: ns, Namespace: r.providerNamespace, OwnerReferences: []metav1.OwnerReference{ *metav1.NewControllerRef(r.apiServiceExport, kubebindv1alpha2.SchemeGroupVersion.WithKind("APIServiceExport")), }, }, }) - if err != nil { - return err - } + if err != nil { + if errors.IsAlreadyExists(err) { + sn, err = r.getServiceNamespace(ns) + if err != nil { + return err + } + } else { + return err + } + }Also applies to: 65-76
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
56-66: Fail fast if apiServiceExport is nil in constructorAdd an early guard to surface misconfiguration before starting informers/queues.
func NewController( - apiServiceExport *kubebindv1alpha2.APIServiceExport, // used to establish owner references when create happens from the consumer side. + apiServiceExport *kubebindv1alpha2.APIServiceExport, // used to establish owner references when create happens from the consumer side. gvr schema.GroupVersionResource, @@ ) (*controller, error) { + if apiServiceExport == nil { + return nil, fmt.Errorf("apiServiceExport must not be nil") + }test/e2e/framework/clients.go (2)
57-61: Unify test helper signatures and mark helpers
- Prefer testing.TB for consistency with other helpers.
- Call t.Helper() for better failure locations.
-func BindClient(t *testing.T, config *rest.Config) bindclientset.Interface { +func BindClient(t testing.TB, config *rest.Config) bindclientset.Interface { + t.Helper() c, err := bindclientset.NewForConfig(config) require.NoError(t, err) return c }
63-67: Keep NewRESTConfig consistent with other helpers
- Switch to testing.TB and add t.Helper().
-func NewRESTConfig(t *testing.T, kubeconfig string) *rest.Config { +func NewRESTConfig(t testing.TB, kubeconfig string) *rest.Config { + t.Helper() config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) require.NoError(t, err, "Failed to build config from kubeconfig file") return config }backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
85-96: Skip RBAC when no permissionClaimsAvoid creating empty Roles/Bindings when an export has no permissionClaims.
- for _, export := range apiServiceExports.Items { + for _, export := range apiServiceExports.Items { + if len(export.Spec.PermissionClaims) == 0 { + continue + } name := fmt.Sprintf("kube-binder-%s-export-%s", sns.Name, export.Name) // per-sns unique name permissions := []rbacv1.PolicyRule{}
166-176: Avoid variable shadowing in Role/RoleBinding creationUse an explicit expected object to avoid shadowing (:=) and improve readability.
- if role == nil { - role := &rbacv1.Role{ + if role == nil { + expected := &rbacv1.Role{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: sns.Status.Namespace, }, Rules: permissions, } // Create new Role - if err := client.Create(ctx, role); err != nil { + if err := client.Create(ctx, expected); err != nil { return fmt.Errorf("failed to create Role %s: %w", name, err) } } else {- if rolebinding == nil { - rolebinding := &rbacv1.RoleBinding{ + if rolebinding == nil { + expected := &rbacv1.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: name, Namespace: sns.Status.Namespace, }, Subjects: []rbacv1.Subject{ { Kind: "ServiceAccount", Namespace: sns.Namespace, Name: kuberesources.ServiceAccountName, }, }, RoleRef: rbacv1.RoleRef{ Kind: "Role", Name: name, APIGroup: "rbac.authorization.k8s.io", }, } - if err := client.Create(ctx, rolebinding); err != nil { + if err := client.Create(ctx, expected); err != nil { return fmt.Errorf("failed to create RoleBinding %s: %w", name, err) } } else {Also applies to: 189-209
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
85-93: Also watch UPDATE events for label-based matchesObjects can become claimed after creation (label changes). Consider handling UpdateFunc to ensure namespaces are created in that scenario too.
if _, err = consumerDynamicInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ AddFunc: func(obj interface{}) { c.enqueueConsumer(logger, obj) }, + UpdateFunc: func(_, newObj interface{}) { + c.enqueueConsumer(logger, newObj) + }, }); err != nil {contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml (1)
490-594: Prevent duplicate permissionClaims entries (enforce uniqueness)Model permissionClaims as a keyed map by group+resource to avoid ambiguous duplicates.
permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: description: |- PermissionClaim selects objects of a GVR that a service provider may request and that a consumer may accept and allow the service provider access to. properties: @@ type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resource x-kubernetes-validations: - message: permissionClaims are immutable rule: self == oldSelfcontrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml (1)
303-404: Add map semantics to status.permissionClaims for stable mergesUse keyed list by group+resource to prevent duplicates and improve SSA behavior.
permissionClaims: description: |- PermissionClaims records decisions about permission claims requested by the service provider. Access is granted per GroupResource. items: @@ type: object type: array + x-kubernetes-list-type: map + x-kubernetes-list-map-keys: + - group + - resourcepkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
419-446: Scope consumer listing to the mapped namespaceAvoid cluster-wide list; query only the relevant consumer namespace.
- objects, err := c.consumerDynamicLister.List(sel) + objects, err := c.consumerDynamicLister.ByNamespace(sn.Name).List(sel) if err != nil { runtime.HandleError(err) return } for _, obj := range objects { // Check if the object is actually claimed using the full selector logic if !c.isClaimed(obj) { continue } - objKey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + objKey, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) if err != nil { runtime.HandleError(err) continue } objNamespace, name, err := cache.SplitMetaNamespaceKey(objKey) if err != nil { runtime.HandleError(err) continue } - // only process objects in this consumer namespace - if objNamespace != "" && objNamespace != sn.Name { - continue - } + // objNamespace must be sn.Name by construction; keep check defensive + if objNamespace != "" && objNamespace != sn.Name { + continue + } provKey := fmt.Sprintf("%s/%s", sn.Status.Namespace, name) logger.V(2).Info("queueing Unstructured", "key", provKey, "reason", "APIServiceNamespace", "ConsumerObject", objKey) c.queue.Add(provKey) }backend/http/handler.go (1)
386-418: Sort UI schemas for deterministic orderMap iteration is random; sort result before templating for stable UI/tests.
result := make([]UISchema, 0, len(exportedSchemas)) for _, item := range exportedSchemas { @@ result = append(result, UISchema{ Name: item.GetName(), Kind: item.Spec.Names.Kind, Scope: string(item.Spec.Scope), Version: ver, Group: item.Spec.Group, // Important: This MUST be used as UI button class in the url, so tests can 'click it' based on it. Resource: item.Spec.Names.Plural, SessionID: sessionID, }) } + // Ensure stable order + sort.Slice(result, func(i, j int) bool { + if result[i].Group != result[j].Group { + return result[i].Group < result[j].Group + } + return result[i].Resource < result[j].Resource + })Add import:
import "sort"Also applies to: 420-427
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (1)
201-214: Nit: remove duplicate SetNamespace callNamespace is set twice; keep one.
- candidate.SetNamespace(downstreamNS) @@ - candidate.SetNamespace(downstreamNS) + candidate.SetNamespace(downstreamNS)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
180-182: String() format doesn't distinguish core group.The format "resource.group" produces "resource." for core APIs (empty group), which may not be the clearest representation. Consider whether core APIs should be formatted differently (e.g., "resource" or "resource.core").
If you want to clarify core group formatting:
func (r GroupResource) String() string { + if r.Group == "" { + return r.Resource + } return fmt.Sprintf("%s.%s", r.Resource, r.Group) }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (46)
Makefile(3 hunks)backend/controllers/clusterbinding/clusterbinding_reconcile.go(1 hunks)backend/controllers/serviceexport/serviceexport_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_controller.go(1 hunks)backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go(6 hunks)backend/controllers/servicenamespace/servicenamespace_controller.go(1 hunks)backend/controllers/servicenamespace/servicenamespace_reconcile.go(3 hunks)backend/http/handler.go(4 hunks)backend/kubernetes/resources/namespace.go(2 hunks)backend/kubernetes/resources/namespace_test.go(1 hunks)contrib/kcp/README.md(1 hunks)contrib/kcp/deploy/bootstrap.go(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml(1 hunks)contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml(1 hunks)contrib/kcp/deploy/examples/cowboy.yaml(1 hunks)contrib/kcp/deploy/examples/sheriff.yaml(1 hunks)contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml(1 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiservicebindings.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml(2 hunks)contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yaml(2 hunks)deploy/crd/kube-bind.io_apiservicebindings.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexportrequests.yaml(1 hunks)deploy/crd/kube-bind.io_apiserviceexports.yaml(1 hunks)pkg/indexers/serviceexport.go(2 hunks)pkg/indexers/servicenamespace.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go(1 hunks)pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/README.md(1 hunks)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go(1 hunks)pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go(5 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go(6 hunks)pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go(11 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go(2 hunks)pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go(3 hunks)pkg/konnector/controllers/contextstore/contextstore.go(1 hunks)pkg/resources/resources.go(1 hunks)pkg/resources/resources_test.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go(1 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go(2 hunks)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go(3 hunks)sdk/apis/kubebind/v1alpha2/boundchema_types.go(4 hunks)sdk/apis/kubebind/v1alpha2/claimable_apis.go(1 hunks)sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go(8 hunks)test/e2e/bind/happy-case_test.go(16 hunks)test/e2e/framework/clients.go(2 hunks)
✅ Files skipped from review due to trivial changes (1)
- pkg/resources/resources_test.go
🚧 Files skipped from review as they are similar to previous changes (14)
- backend/controllers/servicenamespace/servicenamespace_controller.go
- sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go
- sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go
- deploy/crd/kube-bind.io_apiservicebindings.yaml
- contrib/kcp/deploy/bootstrap.go
- pkg/konnector/controllers/contextstore/contextstore.go
- sdk/apis/kubebind/v1alpha2/claimable_apis.go
- contrib/kcp/deploy/examples/apiserviceexport-namespaced.yaml
- backend/kubernetes/resources/namespace_test.go
- backend/controllers/serviceexportrequest/serviceexportrequest_controller.go
- Makefile
- contrib/kcp/deploy/examples/cowboy.yaml
- contrib/kcp/README.md
- contrib/kcp/deploy/resources/apiresourceschema-apiserviceexportrequests.kube-bind.io.yaml
🧰 Additional context used
🧠 Learnings (13)
📓 Common learnings
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
📚 Learning: 2025-09-22T13:32:29.499Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go:255-263
Timestamp: 2025-09-22T13:32:29.499Z
Learning: In kube-bind's claimedresources controller (pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go), the controller does not run if apiServiceExport is not set. This means nil-checks for c.apiServiceExport are unnecessary since the controller lifecycle ensures it's always non-nil when active.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.gopkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.gopkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.gopkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.gopkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go
📚 Learning: 2025-09-12T08:40:15.290Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go:140-143
Timestamp: 2025-09-12T08:40:15.290Z
Learning: APIServiceExportRequest resources in kube-bind are short-lived and automatically deleted after 10 minutes, so they should not be used as owner references for longer-lived resources like BoundSchema. The proper lifecycle management for BoundSchema resources created by APIServiceExportRequest is tracked in issue #297.
Applied to files:
contrib/kcp/deploy/resources/apiresourceschema-apiserviceexports.kube-bind.io.yamlcontrib/kcp/deploy/resources/apiexport-kube-bind.io.yamlbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-19T05:56:35.969Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: backend/controllers/servicenamespace/servicenamespace_reconcile.go:81-98
Timestamp: 2025-09-19T05:56:35.969Z
Learning: In kube-bind, RBAC permissions for PermissionClaims use "*" verbs intentionally. This is a design decision based on: 1) permissions are scoped to consumer-owned provider namespaces, limiting blast radius, 2) bidirectional resource flow requires broad permissions for operations like initial resource creation from consumer side, 3) kube-bind's architecture prioritizes operational simplicity over granular RBAC within the namespace security boundary.
Applied to files:
backend/controllers/servicenamespace/servicenamespace_reconcile.gopkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-19T06:28:44.853Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:148-150
Timestamp: 2025-09-19T06:28:44.853Z
Learning: In kube-bind, permission claims must all have the same scope (either all cluster-scoped or all namespace-scoped). Mixed scopes are not allowed, which means the isClusterScoped flag can be safely determined from any processed schema in the export.
Applied to files:
deploy/crd/kube-bind.io_apiserviceexportrequests.yamlbackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gopkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, there are two different ResourceGroupName() methods: BoundSchema.ResourceGroupName() for CRDs (always non-empty groups) uses simple fmt.Sprintf formatting, while APIServiceExportRequestResource.ResourceGroupName() for export requests handles empty groups by converting to "core". BoundSchema is exclusively for CRDs which cannot have empty API groups per Kubernetes validation.
Applied to files:
contrib/kcp/deploy/examples/apiserviceexport-cluster.yamlbackend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gobackend/controllers/clusterbinding/clusterbinding_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-22T13:20:49.952Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: sdk/apis/kubebind/v1alpha2/boundchema_types.go:49-0
Timestamp: 2025-09-22T13:20:49.952Z
Learning: In kube-bind, BoundSchema.ResourceGroupName() is only used for CRDs (Custom Resource Definitions), and CRDs must always have non-empty API groups. Therefore, handling empty groups (core API group "") is not necessary in this context, unlike general Kubernetes GroupResource handling.
Applied to files:
backend/http/handler.gobackend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-12T09:05:29.762Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/client/listers/kubebind/v1alpha2/boundschema.go:46-48
Timestamp: 2025-09-12T09:05:29.762Z
Learning: In the kube-bind project, lister-gen is generating BoundSchema listers with singular resource names ("boundschema") instead of plural ("boundschemas"), which breaks client-go conventions and can cause cache lookup issues. This is identified as a generator issue that needs upstream investigation rather than manual code fixes.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.gosdk/apis/kubebind/v1alpha2/boundchema_types.go
📚 Learning: 2025-09-12T08:55:41.860Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#295
File: sdk/apis/kubebind/v1alpha2/helpers/boundschema.go:115-123
Timestamp: 2025-09-12T08:55:41.860Z
Learning: In BoundSchemasSpecHash function in sdk/apis/kubebind/v1alpha2/helpers/boundschema.go, silent error handling during JSON encoding (continuing on encoding errors) is acceptable to mjudeikis for the current implementation, even though it could potentially lead to incorrect hash values.
Applied to files:
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. No explicit Cancel() calls are needed when using BulkDeletePrefixed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:27:47.829Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:94-98
Timestamp: 2025-09-23T12:27:47.829Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the BulkDeletePrefixed method automatically cancels all matching contexts before removing them from the store. The Delete method also cancels contexts before removal. No explicit Cancel() calls are needed when using these methods.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts after removing them from the store. The Cancel function is extracted and called outside the lock, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
📚 Learning: 2025-09-23T12:28:06.105Z
Learnt from: mjudeikis
PR: kube-bind/kube-bind#304
File: pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go:175-183
Timestamp: 2025-09-23T12:28:06.105Z
Learning: In kube-bind's contextstore (pkg/konnector/controllers/contextstore/contextstore.go), the Store.Delete(key) method automatically cancels stored contexts before removing them from the store. The Cancel function is called internally, so explicit cancellation before deletion is not needed.
Applied to files:
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go
🧬 Code graph analysis (21)
pkg/indexers/serviceexport.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (4)
pkg/indexers/util.go (1)
AddIfNotPresentOrDie(49-60)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/konnector/controllers/contextstore/contextstore.go (1)
New(59-63)pkg/indexers/serviceexport.go (2)
ServiceExportByBoundSchema(27-27)IndexServiceExportByBoundSchema(54-65)
pkg/indexers/servicenamespace.go (1)
sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_reconcile.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
backend/controllers/servicenamespace/servicenamespace_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
ClusterScope(62-62)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExportList(140-145)
test/e2e/bind/happy-case_test.go (5)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
ClusterScope(62-62)NamespacedScope(63-63)InformerScope(59-59)test/e2e/framework/clients.go (2)
KubeClient(39-43)BindClient(57-61)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (5)
APIServiceExportRequest(46-60)PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)pkg/konnector/controllers/cluster/serviceexport/cluster-scoped/utils.go (2)
ExtractClusterNs(97-104)Prepend(33-35)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespaceList(64-69)
backend/controllers/serviceexport/serviceexport_controller.go (1)
pkg/indexers/serviceexport.go (1)
IndexServiceExportByBoundSchemaControllerRuntime(40-51)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (1)
sdk/apis/kubebind/v1alpha2/register.go (1)
Resource(42-44)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (5)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)pkg/resources/resources.go (1)
IsClaimed(28-69)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (2)
sdk/apis/kubebind/v1alpha2/apiservicebinding_types.go (1)
APIServiceBinding(64-74)sdk/apis/kubebind/v1alpha2/apiserviceexport_types.go (1)
APIServiceExport(57-68)
pkg/resources/resources.go (1)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
Selector(134-142)NamedResource(145-159)
backend/http/handler.go (2)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ClusterScope(62-62)ExportedSchemas(32-32)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)
test/e2e/framework/clients.go (1)
sdk/client/clientset/versioned/typed/kubebind/v1alpha2/kubebind_client.go (1)
NewForConfig(71-79)
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (6)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
BoundSchema(41-47)ExportedSchemas(32-32)InformerScope(59-59)sdk/apis/third_party/conditions/util/conditions/setter.go (3)
SetSummary(126-128)Set(41-78)MarkFalse(120-122)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (6)
Selector(134-142)APIServiceExportRequest(46-60)APIServiceExportRequestConditionExportsReady(31-31)GroupResource(162-178)APIServiceExportRequestPhaseFailed(218-218)PermissionClaim(186-193)sdk/apis/kubebind/v1alpha2/helpers/boundschema.go (1)
UnstructuredToBoundSchema(113-119)sdk/apis/third_party/conditions/util/conditions/getter.go (1)
GetMessage(94-99)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ClaimableAPIs(38-81)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_reconciler.go (2)
sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (3)
OwnerProvider(200-200)OwnerConsumer(202-202)Owner(196-196)
backend/controllers/clusterbinding/clusterbinding_reconcile.go (1)
sdk/apis/kubebind/v1alpha2/register.go (1)
GroupName(32-32)
sdk/apis/kubebind/v1alpha2/boundchema_types.go (1)
sdk/apis/third_party/conditions/apis/conditions/v1alpha1/types.go (1)
Conditions(92-92)
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (8)
pkg/konnector/controllers/contextstore/contextstore.go (4)
Store(40-46)Key(27-27)NewKey(33-38)SyncContext(53-57)sdk/apis/kubebind/v1alpha2/boundchema_types.go (3)
InformerScope(59-59)ClusterScope(62-62)BoundSchema(41-47)pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (1)
NewController(53-184)pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)pkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (2)
NewController(52-143)Resource(145-145)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
ResolveClaimableAPI(83-90)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
PermissionClaim(186-193)Selector(134-142)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (3)
GetterInformer(49-56)GetterInformerWrapper(305-308)NewDynamicMultiNamespaceInformer(77-104)
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (3)
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)NamedResource(145-159)GroupResource(162-178)Selector(134-142)sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
ExportedSchemas(32-32)BoundSchema(41-47)sdk/apis/kubebind/v1alpha2/claimable_apis.go (1)
InternalAPI(29-35)
pkg/konnector/controllers/cluster/claimedresources/claimedresources_controller.go (8)
pkg/konnector/controllers/cluster/claimedresourcesnamespaces/claimedresourcesnamespaces_controller.go (1)
NewController(48-95)sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (4)
PermissionClaim(186-193)GroupResource(162-178)Selector(134-142)NamedResource(145-159)sdk/client/informers/externalversions/generic.go (1)
GenericInformer(32-35)pkg/konnector/controllers/cluster/serviceexport/multinsinformer/informer.go (1)
GetterInformer(49-56)pkg/konnector/controllers/dynamic/eventhandler.go (1)
Informer(40-43)sdk/apis/kubebind/v1alpha1/apiservicenamespace_types.go (1)
APIServiceNamespace(41-50)pkg/indexers/servicenamespace.go (1)
ServiceNamespaceByNamespace(26-26)pkg/resources/resources.go (1)
IsClaimed(28-69)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: go-test
- GitHub Check: go-test-e2e
- GitHub Check: verify
- GitHub Check: lint
- GitHub Check: Generate and push docs
🔇 Additional comments (34)
pkg/indexers/servicenamespace.go (1)
26-26: No updates needed for constant visibility
The constant remains exported (ServiceNamespaceByNamespace), so external references are unaffected.Likely an incorrect or invalid review comment.
pkg/konnector/controllers/cluster/servicebinding/servicebinding_reconcile.go (3)
119-134: Deep copy addresses informer cache mutation concern.Good change! By fetching the export and immediately creating a deep copy (line 134), you've addressed the previous review concern about mutating shared objects from the informer cache. The
exportvariable used throughout the rest of the function is now a safe local copy.
211-214: Permission claims assignment is now safe but see loop concern.The deep copy of
exportat line 134 ensures thatexport.Spec.PermissionClaimsis no longer a reference to the shared informer cache, so this assignment is safe. However, this method is currently called inside the schema processing loop (line 157), which is inefficient. See the separate comment on lines 157-159.
297-300: LGTM on variable rename.The loop variable rename from
restorefand corresponding usage update (ref.ResourceGroupName()) improves clarity. The logic remains correct.backend/controllers/clusterbinding/clusterbinding_reconcile.go (1)
161-165: LGTM! RBAC rule for BoundSchema status updates.The addition of get/update/patch permissions for the
boundschemas/statussubresource is appropriate for supporting status reconciliation in the PermissionClaims feature.backend/kubernetes/resources/namespace.go (2)
35-54: LGTM! Backward-compatible annotation migration.The migration logic correctly handles the transition from the legacy annotation key to the new one. The function uses atomic patching and properly cleans up the legacy annotation after migration.
78-84: LGTM! Dual-key identity check with migration.The updated logic correctly validates identity against both the current and legacy annotation keys, then migrates if needed. This ensures backward compatibility during upgrades.
contrib/kcp/deploy/examples/sheriff.yaml (1)
4-6: LGTM! Example data update.The addition of the
spec.intentfield enriches the example Sheriff resource.contrib/kcp/deploy/examples/apiserviceexport-cluster.yaml (1)
11-17: LGTM! PermissionClaims example demonstrates the feature.The example correctly demonstrates the new PermissionClaims functionality with a label selector for cluster-scoped ConfigMaps. The field names match the API (using
groupnotapiGroup).backend/controllers/serviceexport/serviceexport_controller.go (1)
62-62: LGTM! Updated to use renamed indexer function.The change aligns with the indexer refactoring in
pkg/indexers/serviceexport.go, using the controller-runtime compatible function name.contrib/kcp/deploy/resources/apiexport-kube-bind.io.yaml (1)
52-62: LGTM! Schema versions updated for PermissionClaims support.The schema version updates for
apiservicebindings,apiserviceexportrequests, andapiserviceexportsalign with the new PermissionClaims functionality introduced in this PR.pkg/konnector/controllers/cluster/serviceexport/spec/spec_controller.go (1)
56-66: spec.NewController calls include the new apiServiceExport argumentpkg/konnector/controllers/cluster/serviceexport/serviceexport_controller.go (1)
80-83: Indexer wiring and CRD handler look correct
- Registers the right indexers and uses the BoundSchema index for CRD-triggered queueing. Error handling is appropriate (ByIndex won’t return NotFound).
Also applies to: 124-126, 187-208
backend/controllers/servicenamespace/servicenamespace_reconcile.go (1)
93-95: Note: wildcard verbs are intentional hereUsing Verbs ["*"] matches kube-bind’s simplified permission model within the provider namespace boundary. No change requested. Based on learnings
sdk/apis/kubebind/v1alpha2/boundchema_types.go (2)
30-33: ExportedSchemas map looks goodKeying by "resource.group" aligns with callers (e.g., HTTP handler). Comment is accurate.
49-54: ResourceGroupName implementation is appropriateFormatting as "." is correct for CRDs where group is non-empty. Based on learnings.
sdk/apis/kubebind/v1alpha2/zz_generated.deepcopy.go (5)
20-29: LGTM! Auto-generated deepcopy code.The deepcopy-gen has correctly added the metav1 import for LabelSelector deep copy support.
181-187: LGTM! Correct deep copy for PermissionClaims slice.The auto-generated code properly allocates the slice and performs per-element deep copy for nested structures.
971-999: LGTM! Correct map deep copy with pointer handling.The auto-generated deep copy for ExportedSchemas properly handles the map-of-pointers structure with nil safety.
1017-1036: LGTM! Correct deep copy for InternalAPI with runtime.Object handling.The auto-generated code properly handles the runtime.Object interface field using DeepCopyObject().
1070-1144: LGTM! Correct deep copy implementations for PermissionClaim types.The auto-generated code properly handles:
- Embedded struct copy in PermissionClaim
- Slice and pointer field copy in Selector with metav1.LabelSelector.DeepCopyInto
- Simple value struct copy in NamedResource
sdk/apis/kubebind/v1alpha2/apiserviceexportrequest_types.go (2)
132-142: LGTM! Selector allows flexible resource selection.The Selector type appropriately supports both named resources and label-based selection with AND semantics.
195-207: LGTM! Simple and clear Owner type definition.The Owner type and constants provide clear ownership semantics.
pkg/konnector/controllers/cluster/serviceexport/serviceexport_reconcile.go (6)
69-86: LGTM! Clean signature update with explicit namespace parameter.The addition of the namespace parameter improves clarity and makes the reconcile flow more explicit.
88-100: LGTM! Correct cleanup using context store.The cleanup correctly uses BulkDeletePrefixed, which automatically cancels contexts per established learnings.
Based on learnings
171-298: LGTM! Schema controller lifecycle correctly tracks export generation.The controller lifecycle management properly uses export.Generation for tracking (line 293), ensuring correct synchronization with the export resource.
300-352: LGTM! Well-structured permission claim controller lifecycle.The ensureControllersForPermissionClaims method properly:
- Tracks processed claims for cleanup
- Checks generation for updates
- Cleans up stale controllers using contextstore
Based on learnings
354-504: LGTM! Excellent permission claim controller implementation.The ensureControllerForPermissionClaim method demonstrates excellent design:
- Precomputes label selector to fail closed (lines 368-377), addressing past review feedback
- Comprehensive comment explaining 4 informer configurations (lines 393-406)
- Proper cleanup with defer (line 475)
- Correct informer and controller lifecycle management
The detailed comment explaining the 1A, 1B, B1, B2 cases is particularly helpful for maintainability.
506-588: LGTM! Condition messages updated for BoundSchemas terminology.The error messages correctly reference "BoundSchemas" terminology, consistent with the broader API changes.
backend/controllers/serviceexportrequest/serviceexportrequest_reconcile.go (5)
55-80: LGTM! Improved error handling with contextual wrapping.The reconcile flow now provides better error context through wrapping, and the comment clearly explains why schemas must be created before validation.
82-130: LGTM! Well-documented client-based schema listing.The getExportedSchemas method correctly uses client.Client for dynamic unstructured queries, with a clear comment explaining why cache informers don't work for this use case.
216-242: LGTM! Permission claims correctly propagated to export.Line 241 properly copies PermissionClaims from request to export spec. Since PermissionClaims are immutable per API validation, slice sharing is safe.
268-359: LGTM! Comprehensive validation logic.The validate method properly checks:
- Schema existence
- Scope uniformity (based on learnings that mixed scopes aren't allowed)
- Permission claim validity against ClaimableAPIs
- Duplicate detection
The TODO comment (lines 272-273) correctly notes this validation should eventually move to a validating admission webhook for better user experience.
Based on learnings
361-369: LGTM! Clean helper for checking claimable APIs.The isClaimableAPI helper provides a clear, simple check against the list of supported claimable APIs.
| return fmt.Errorf("no exported schemas found") | ||
| } | ||
|
|
||
| first := apiextensionsv1.ResourceScope("") |
There was a problem hiding this comment.
nit: we could just use var first apiextensionsv1.ResourceScope
There was a problem hiding this comment.
will address in follow-up
|
|
||
| // NewController returns a new controller reconciling downstream objects to upstream. | ||
| func NewController( | ||
| // scope kubebindv1alpha2.InformerScope, |
There was a problem hiding this comment.
will address in follow-up
Summary
This adds the ability to claim related resources. For now, it's ServiceAccounts, ConfigMaps and Secrets.
Claims are label-based.
The origin of the resources determines the owner.
High-level design:
Permission Claims are specified using
GVRtype syntax, same as resource Claims.LabelSelectors are used to scope down the resources. Based on label selectors, informers will be constructed.
Label selector is immutable and should be the provider lead. This means the provider determines what it is, and the consumer has no say in the matter. In the future, we could allow consumers to say, but we would need to ship admissionWebhook. And I want to avoid this complexity now.
Permission on provider side - Its either
*on the namespace level (when isolation is namespaced, or same on cluster level).This is mainly as verbs dont quite make sense in kube-bind sense. As you always need more than what might imply. In the end, Kube verbs are ineffective because they are based on informers, requiring specific permissions (list, get, watch) to be used. Since the permission model only applies to
konnectortoprovideronly, even if the provider states it needsread(list, watch, get), it is not applicable.I suggest deferring permission to future enhancement if needed, and even then, make them super flat (read, write, all). But for now, let's keep the API super simple.
kube-bind.io/owner=provider/consumerTo see this in action, follow https://github.com/kube-bind/kube-bind/blob/main/contrib/kcp/README.md from the PR.
Permission claim example:
What Type of PR Is This?
/kind feature
/kind api-change
Related Issue(s)
Based on #295 and it must merge firstFixes: #256
Release Notes
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores