Skip to content

apikey status controller#46

Open
eguzki wants to merge 6 commits intodeveloper-portal-rbacfrom
apikey-status-controller
Open

apikey status controller#46
eguzki wants to merge 6 commits intodeveloper-portal-rbacfrom
apikey-status-controller

Conversation

@eguzki
Copy link
Copy Markdown
Contributor

@eguzki eguzki commented Apr 13, 2026

Dependes on #45

Part of #39

Adds three new reconcilers:

  • apikey_status_controller: reconcile apikey status based on the new approval workflow
  • apikey_approval_controller: reconcile apikeyapprovals to add ownerref to apikeyrequest.
  • apikey_approval_status_controller: reconcile apikeyapproval status with conditions that validate the resource

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced API Key status tracking with observed generation and condition-based state updates
    • API Key Approval status now supports conditions and generation tracking
    • Automatic discovery of API hostnames from Gateway configuration
    • Improved status synchronisation across related resources
  • Documentation

    • Added Entity Relationship Model documentation describing resource relationships and patterns

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

📝 Walkthrough

Walkthrough

Three new Kubernetes controllers are introduced: APIKeyStatusReconciler updates APIKey status based on referenced APIProduct, Secret, and gateway resources; APIKeyApprovalReconciler manages owner references between APIKeyApproval and APIKeyRequest; APIKeyApprovalStatusReconciler maintains APIKeyApproval validation status. Type definitions gain ObservedGeneration and Conditions fields, CRD schemas are updated accordingly, RBAC rules adjusted, and comprehensive test coverage is added.

Changes

Cohort / File(s) Summary
API Type Definitions
api/v1alpha1/apikey_types.go, api/v1alpha1/apikeyapproval_types.go, api/v1alpha1/zz_generated.deepcopy.go
Added ObservedGeneration field to APIKeyStatus and APIKeyApprovalStatus; added Conditions field to APIKeyApprovalStatus with new APIKeyApprovalConditionValid constant; added APIProductKey() method to APIKey; updated deep copy logic for proper slice/struct copying of conditions.
CRD Schemas
config/crd/bases/devportal.kuadrant.io_apikeys.yaml, config/crd/bases/devportal.kuadrant.io_apikeyapprovals.yaml
Extended status schema for both CRDs to include observedGeneration (int64) and conditions array with standard condition object properties (type, status, reason, message, lastTransitionTime).
APIKey Status Reconciler
internal/controller/apikey_status_controller.go, internal/controller/apikey_status_controller_test.go
New reconciler that computes and updates APIKey status based on referenced APIProduct, Secret, and HTTPRoute resources; sets Failed/Denied/Approved conditions and populates AuthScheme, Limits, and APIHostname fields; includes comprehensive test suite covering missing resources, approval states, and multi-namespace scenarios.
APIKeyApproval Controllers
internal/controller/apikeyapproval_controller.go, internal/controller/apikeyapproval_controller_test.go, internal/controller/apikeyapproval_status_controller.go, internal/controller/apikeyapproval_status_controller_test.go
Two controllers: one establishes owner references from APIKeyApproval to APIKeyRequest; another maintains Valid condition status based on APIKeyRequest existence/deletion state; both include test suites validating idempotency, namespace isolation, and status transitions.
Refactoring & Helpers
internal/controller/apikeyrequest_controller.go, internal/controller/apikeyrequest_controller_test.go, internal/controller/apikeyrequest_status_controller_test.go, internal/controller/devportal_helper.go, internal/controller/suite_test.go
Centralised APIKeyRequest naming via APIKeyRequestName() helper; added context-based storage/retrieval functions (With*/Get*) for APIKey, APIKeyRequest, and APIKeyApproval lists; added cleanup helper for APIKeyApproval resources in test suite.
Configuration & Setup
cmd/main.go, config/rbac/role.yaml
Registered three new controller instances in main reconciliation loop with error handling; restructured RBAC rules to isolate apikeyapprovals resource with patch/status permission separate from other resources.
Documentation
README.md, AGENTS.md
Added Entity Relationship Model section with Mermaid graph and key concepts documentation; created new AGENTS.md placeholder file.

Sequence Diagram(s)

sequenceDiagram
    participant Reconciler as APIKeyStatusReconciler
    participant Client as Kubernetes API
    participant Context as Context Storage
    
    Reconciler->>Client: List all APIKeys
    Reconciler->>Context: Store APIKey list
    Reconciler->>Client: List all APIKeyRequests
    Reconciler->>Context: Store APIKeyRequest list
    Reconciler->>Client: List all APIKeyApprovals
    Reconciler->>Context: Store APIKeyApproval list
    Reconciler->>Client: List all APIProducts
    Reconciler->>Client: List all HTTPRoutes
    
    loop For each active APIKey
        Reconciler->>Client: Get referenced APIProduct
        Reconciler->>Client: Get referenced Secret
        Reconciler->>Client: Get referenced HTTPRoute
        
        alt APIProduct missing or deleting
            Reconciler->>Reconciler: Set Failed condition
        else Secret missing or invalid
            Reconciler->>Reconciler: Set Failed condition
        else APIKeyApproval valid
            Reconciler->>Reconciler: Set Approved condition
        else APIKeyApproval rejected
            Reconciler->>Reconciler: Set Denied condition
        end
        
        Reconciler->>Reconciler: Extract AuthScheme from APIProduct
        Reconciler->>Reconciler: Extract Limits from APIProduct plans
        Reconciler->>Reconciler: Extract APIHostname from HTTPRoute
        
        Reconciler->>Client: Update APIKey status (if changed)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 Three controllers hop and bound,
Reconciling status all around,
APIKeys find their way to truth,
With Approvals validating proof,
Conditions bloom in fields so bright! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'apikey status controller' is related to the primary changes, which include a new APIKeyStatusReconciler controller and associated status management logic for APIKey resources.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch apikey-status-controller

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread README.md
Comment on lines +404 to +405
APIKeyRequest -->|"spec.apiProductRef<br/>(local)"| APIProduct
APIKeyApproval -->|"spec.apiKeyRequestRef<br/>(local)"| APIKeyRequest
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could simplify the dance between APIKeyRequest and APIKeyApproval within the same object, maybe APIKeyRequest or a new name like APIKeyManagement

@eguzki eguzki force-pushed the apikey-status-controller branch from 82afe95 to 4e541db Compare April 13, 2026 17:46
@eguzki eguzki force-pushed the APIKeyApproval-crd branch 2 times, most recently from d3f8420 to dcee43e Compare April 16, 2026 14:15
@eguzki eguzki force-pushed the apikey-status-controller branch from 4e541db to 2881bcd Compare April 16, 2026 14:20
@eguzki eguzki force-pushed the APIKeyApproval-crd branch from dcee43e to f8701e7 Compare April 17, 2026 13:38
@eguzki eguzki force-pushed the apikey-status-controller branch from 2881bcd to 9f85ac1 Compare April 17, 2026 13:39
@R-Lawton
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@R-Lawton R-Lawton mentioned this pull request Apr 17, 2026
@R-Lawton
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (11)
internal/controller/devportal_helper.go (1)

10-16: Nit: key type naming inconsistency.

apiKeyRequestCtxKey (singular) uses type apiKeyRequestsCtxKeyType (plural), while the other two key constants match their type names (apiKeysCtxKey/apiKeysCtxKeyType, apiKeyApprovalsCtxKey/apiKeyApprovalsCtxKeyType). Rename to apiKeyRequestsCtxKey for consistency.

-const apiKeyRequestCtxKey apiKeyRequestsCtxKeyType = "apikeyrequests"
+const apiKeyRequestsCtxKey apiKeyRequestsCtxKeyType = "apikeyrequests"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/devportal_helper.go` around lines 10 - 16, The constant
name apiKeyRequestCtxKey is inconsistent with its type apiKeyRequestsCtxKeyType;
rename the constant to apiKeyRequestsCtxKey and update all usages to match so
the pattern aligns with apiKeysCtxKey/apiKeysCtxKeyType and
apiKeyApprovalsCtxKey/apiKeyApprovalsCtxKeyType; ensure you update references in
functions or middleware that read/write context keys (look for
apiKeyRequestCtxKey occurrences) to the new apiKeyRequestsCtxKey identifier.
README.md (1)

400-444: LGTM — the Entity Relationship Model section and Mermaid diagram usefully summarise namespace boundaries and the shadow-resource pattern, and are consistent with the per-resource descriptions above (cross-namespace APIKey → APIProduct and APIKeyRequest → APIKey, local APIKeyRequest → APIProduct and APIKeyApproval → APIKeyRequest, controller-created APIKey ⇢ APIKeyRequest).

Tiny nit: the "Cross-Namespace References (solid arrows)" / "Local References (solid arrows)" bullets both say "solid arrows"; consider distinguishing them visually in the diagram (e.g., different stroke styles) or rewording the legend, since as written both solid-arrow categories look identical to a reader.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 400 - 444, The legend currently labels both
cross-namespace and local references as "solid arrows", which is ambiguous;
update the Mermaid diagram legend or the diagram itself so they are
distinguishable: either reword the bullets to "Cross-Namespace References (solid
arrows)" and "Local References (solid arrows — same-namespace)" or change the
local-reference arrow style in the diagram (the edges APIKey --> APIProduct and
APIKeyRequest --> APIProduct for cross-namespace vs APIKeyRequest --> APIProduct
and APIKeyApproval --> APIKeyRequest for local) to a visually different stroke
(e.g., use a different arrow style or class) and reflect that change in the
legend so readers can clearly tell cross-namespace vs local links.
internal/controller/apikey_status_controller.go (4)

493-497: Nit: redundant string(...) conversion on a string literal.

"apikey-status" is already a string, so the explicit conversion is unnecessary.

📝 Proposed fix
-	return []ctrl.Request{{NamespacedName: types.NamespacedName{
-		Name: string("apikey-status"),
-	}}}
+	return []ctrl.Request{{NamespacedName: types.NamespacedName{
+		Name: "apikey-status",
+	}}}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikey_status_controller.go` around lines 493 - 497, In
enqueueClass, remove the redundant string conversion around the literal by
changing Name: string("apikey-status") to simply Name: "apikey-status" inside
the APIKeyStatusReconciler.enqueueClass function so the NamespacedName.Name is
assigned the literal directly.

120-124: Nit: log message mentions apiproduct instead of apikey.

Also a copy-paste artefact — misleading when grepping logs.

📝 Proposed fix
-		logger.V(1).Info("apiproduct status unchanged, skipping update")
+		logger.V(1).Info("apikey status unchanged, skipping update")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikey_status_controller.go` around lines 120 - 124, The
log message in the status-check branch uses a copy-pasted string "apiproduct
status unchanged, skipping update" which is misleading; update the logger call
in the block that computes equalStatus (using
equality.Semantic.DeepEqual(newStatus, apiKey.Status) and compares
apiKey.Generation to apiKey.Status.ObservedGeneration) to reference "apikey"
instead of "apiproduct" (e.g., logger.V(1).Info with a corrected message) so
logs accurately reflect the checked resource.

45-49: Nit: stale doc comment references APIProductReconciler.

The struct is APIKeyStatusReconciler, so the preceding comment is a copy-paste leftover from apiproduct_controller.go.

📝 Proposed fix
-// APIProductReconciler reconciles a APIProduct object
+// APIKeyStatusReconciler reconciles APIKey status based on referenced APIProduct, Secret, and gateway resources
 type APIKeyStatusReconciler struct {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikey_status_controller.go` around lines 45 - 49, The
top-of-file doc comment is stale: change the comment that currently reads
"APIProductReconciler reconciles a APIProduct object" to correctly describe the
APIKeyStatusReconciler (e.g., "APIKeyStatusReconciler reconciles an APIKeyStatus
object") or remove it; update the comment immediately preceding the
APIKeyStatusReconciler struct declaration so it matches the struct name
APIKeyStatusReconciler and its purpose.

478-480: Orphan comment left over outside any function.

This trailing comment appears to be a leftover; it isn't attached to a declaration and just adds noise.

🧹 Proposed fix
-// Clear all status condition types (Approved, Denied, Failed)
-
 // SetupWithManager sets up the controller with the Manager.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikey_status_controller.go` around lines 478 - 480,
Remove the orphan trailing comment "Clear all status condition types (Approved,
Denied, Failed)" from the file (it sits outside any function) or, if the intent
was to document behavior, move that text immediately above the function that
actually clears status conditions (search for the method that handles status
condition clearing or any function named with "status", "condition", "Clear", or
"Approve/Denie/Fail" in apikey_status_controller.go) so it becomes a proper doc
comment for that function.
api/v1alpha1/apikey_types.go (1)

39-42: Minor: namespace fallback in APIProductKey() is unreachable given the current schema.

APIProductReference.Namespace has no omitempty/+optional marker, so it is required by the CRD and will always be populated when decoded from the API server. The if a.Spec.APIProductRef.Namespace != "" fallback to a.Namespace is therefore dead. Either mark Namespace as optional with omitempty (if cross-namespace-by-default-to-APIKey is the intended UX) or drop the fallback to keep intent explicit.

Also applies to: 129-142

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/v1alpha1/apikey_types.go` around lines 39 - 42, The fallback path in
APIProductKey() that checks APIProductRef.Namespace is unreachable because
APIProductReference.Namespace is required by the CRD (no
`omitempty`/`+optional`), so it will always be populated; either make Namespace
optional on the struct (add `omitempty`/`+optional` to
APIProductReference.Namespace) if you want empty to mean "use a.Spec.Namespace",
or remove the dead fallback logic in the APIProductKey() method to keep behavior
explicit. Locate APIProductReference.Namespace and APIProductKey() (and the
similar code block at the other occurrence around the 129-142 region) and apply
the same change consistently.
internal/controller/apikeyapproval_controller_test.go (1)

59-59: Nit: ctx := context.Background() is shared across all specs.

This is captured once at container-construction time rather than per-spec. It works because context.Background() is effectively immutable, but it means the test cannot be cancelled or time-bounded via SpecContext. Consider using the SpecContext already passed to BeforeEach/AfterEach/It (as you do at lines 43, 48, 108) for the inner k8sClient calls so that per-spec timeouts propagate correctly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikeyapproval_controller_test.go` at line 59, The test
creates a package-level/static context with `ctx := context.Background()` which
prevents per-spec cancellation and timeouts; remove that global Background
context and instead use the SpecContext provided to each spec (the `ctx`
parameter passed into BeforeEach/AfterEach/It) when calling k8sClient methods
and constructing resources (e.g., replace uses of the global `ctx` in
k8sClient.Create/Delete/Update calls and any container/resource construction
with the per-spec `ctx` variable passed into those hooks).
internal/controller/apikeyapproval_controller.go (1)

112-124: Minor: UID-only check may miss the Controller: true flag; consider metav1.IsControlledBy.

The loop accepts any existing OwnerReference with the matching UID, including non-controller refs. In practice SetControllerReference also rejects a second controller, so the current code is effectively correct, but using the built-in helper makes intent explicit and keeps you aligned with controller-runtime conventions.

🧹 Proposed tidy-up
-	// Check if owner reference is already set
-	for _, ownerRef := range approval.OwnerReferences {
-		if ownerRef.UID == foundRequest.UID {
-			// Owner reference already set
-			return nil
-		}
-	}
+	// Already the controller of this approval? Nothing to do.
+	if metav1.IsControlledBy(approval, foundRequest) {
+		return nil
+	}

This requires importing metav1 "k8s.io/apimachinery/pkg/apis/meta/v1".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikeyapproval_controller.go` around lines 112 - 124, The
current loop over approval.OwnerReferences only compares UID
(approval.OwnerReferences and foundRequest.UID) and can treat non-controller
ownerRefs as controllers; replace that manual UID check with the helper
metav1.IsControlledBy(approval, foundRequest) to explicitly test controller
ownership before returning, and ensure you import metav1
("k8s.io/apimachinery/pkg/apis/meta/v1"); keep the subsequent
controllerutil.SetControllerReference(foundRequest, approval, r.Scheme) logic
unchanged so SetControllerReference still runs when no controller owner is
present.
internal/controller/apikeyapproval_status_controller.go (2)

198-202: Nit: redundant string(...) conversion of a string literal.

string("apikeyapproval-status") is a no-op. The same pattern appears in internal/controller/apikeyapproval_controller.go line 147.

🧹 Proposed tidy-up
 func (r *APIKeyApprovalStatusReconciler) enqueueClass(_ context.Context, _ client.Object) []ctrl.Request {
 	return []ctrl.Request{{NamespacedName: types.NamespacedName{
-		Name: string("apikeyapproval-status"),
+		Name: "apikeyapproval-status",
 	}}}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikeyapproval_status_controller.go` around lines 198 -
202, The Name field is using a redundant string(...) conversion in enqueueClass;
change Name: string("apikeyapproval-status") to Name: "apikeyapproval-status"
(remove the unnecessary string() cast) and apply the same tidy-up to the
identical pattern found in the related enqueueClass implementation in the
APIKeyApproval reconciler to remove the no-op conversions.

77-87: Single failing approval aborts the rest of the batch.

Any non-conflict error from reconcileStatus returns immediately, so remaining approvals in the list are not reconciled in this pass. Controller-runtime will requeue on error, but if one approval consistently fails the others effectively stall. Consider aggregating errors (e.g. via errors.Join/kerrors.NewAggregate) and continuing the loop.

🛠️ Suggested pattern
+	var errs []error
 	for idx := range activeAPIKeyApprovalList {
-		err := r.reconcileStatus(ctx, &activeAPIKeyApprovalList[idx])
-		if err != nil {
+		if err := r.reconcileStatus(ctx, &activeAPIKeyApprovalList[idx]); err != nil {
 			if apierrors.IsConflict(err) {
-				// Ignore conflicts, resource might just be outdated.
-				logger.Info("failed to update status: resource might just be outdated")
+				logger.Info("failed to update status: resource might just be outdated",
+					"apikeyapproval", client.ObjectKeyFromObject(&activeAPIKeyApprovalList[idx]))
 				return ctrl.Result{Requeue: true}, nil
 			}
-			return ctrl.Result{}, err
+			errs = append(errs, err)
 		}
 	}
-	return ctrl.Result{}, nil
+	return ctrl.Result{}, errors.Join(errs...)

(Also note the existing log line omits the offending approval’s key, unlike the sibling controller on line 78 of apikeyapproval_controller.go.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controller/apikeyapproval_status_controller.go` around lines 77 -
87, The loop over activeAPIKeyApprovalList should not return on the first
non-conflict error; instead, continue processing remaining approvals while
aggregating errors: call reconcileStatus(ctx, &activeAPIKeyApprovalList[idx])
for each item, log failures including the approval key (use the same key format
as apikeyapproval_controller.go), skip apierrors.IsConflict by requeueing that
case, and collect other errors into a single aggregate (using errors.Join or
kerrors.NewAggregate) to return after the loop so other approvals are reconciled
in this pass; update the logger call to include the offending approval's key and
any error details.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/controller/apikey_status_controller.go`:
- Line 219: Replace direct uses of the embedded client selector (r.Client.Get)
with the short form (r.Get) to satisfy staticcheck QF1008; specifically update
the calls inside APIKeyStatusReconciler where r.Client.Get(...) appears (e.g.,
the call at the location that currently calls err := r.Client.Get(ctx,
apiProductKey, apiProduct) and the similar call around line 258) to use
r.Get(ctx, apiProductKey, apiProduct) instead so the reconciler uses the
embedded client directly (consistent with other methods like getAPIProduct and
apiHostName).

In `@internal/controller/apikeyapproval_status_controller_test.go`:
- Around line 218-261: The test never exercises the controller's
activeAPIKeyApprovalList deletion-timestamp filter because the created
APIKeyApproval has no finalizer and is immediately removed by k8sClient.Delete;
update the test to add a finalizer to the approval object (the local variable
approval) before calling k8sClient.Delete so the object stays in the API server
with a deletionTimestamp and triggers the controller path in
APIKeyApprovalStatusReconciler that uses activeAPIKeyApprovalList; after the
test remove the finalizer (teardown) so cleanup succeeds, and adjust the
Consistently closure to not treat a NotFound as a successful short-circuit
(i.e., only return true when the resource exists and has zero conditions) so the
assertion actually verifies the skipped update behavior.

In `@internal/controller/apikeyapproval_status_controller.go`:
- Around line 122-135: The existing code removes the current Valid condition
(baseConditions) before calling meta.SetStatusCondition, causing
LastTransitionTime to be reset on every reconcile; instead preserve all existing
conditions by cloning approval.Status.Conditions into newStatus.Conditions (use
slices.Clone(approval.Status.Conditions)), then compute validCondition via
calculateValidCondition(ctx, approval) and call
meta.SetStatusCondition(&newStatus.Conditions, *validCondition) as needed so
SetStatusCondition can detect real transitions; drop the lo-based filtering and
remove the lo import if unused.
- Around line 45-46: The RBAC marker for the status subresource only grants
"patch" but the controller calls r.Client.Status().Update(ctx, approval), which
requires the "update" verb; either add "update" to the kubebuilder RBAC marker
for apikeyapprovals/status (extend the verbs to "patch;update" or
"update;patch") or change the controller call to use
r.Client.Status().Patch(...) instead of Status().Update(...); locate the marker
lines with "+kubebuilder:rbac:...resources=apikeyapprovals/status,verbs=patch"
and the status write call r.Client.Status().Update(ctx, approval) to make the
corresponding change.
- Around line 100-104: The equality check is comparing a pointer to a value so
it always returns false; change the guard to compare the concrete values by
dereferencing newStatus (use equality.Semantic.DeepEqual(*newStatus,
approval.Status)) and then you can remove the redundant Generation check (or
keep it optional) so the "apikeyapproval status unchanged, skipping update"
branch can fire correctly; update the block around newStatus, approval.Status
and the logger.V(1).Info call accordingly.

In `@internal/controller/devportal_helper.go`:
- Around line 22-52: The Get* helpers currently return newly allocated empty
lists which prevents caller nil-checks from detecting missing context wiring;
change GetAPIKeys, GetAPIKeyRequests, and GetAPIKeyApprovals to return nil when
the context lookup or type assertion fails (i.e., if
ctx.Value(apiKeysCtxKey)/apiKeyRequestCtxKey/apiKeyApprovalsCtxKey is missing or
not the expected type) so existing callers that do "if x == nil { return
fmt.Errorf(... not found in context) }" behave correctly; no caller changes
required.

---

Nitpick comments:
In `@api/v1alpha1/apikey_types.go`:
- Around line 39-42: The fallback path in APIProductKey() that checks
APIProductRef.Namespace is unreachable because APIProductReference.Namespace is
required by the CRD (no `omitempty`/`+optional`), so it will always be
populated; either make Namespace optional on the struct (add
`omitempty`/`+optional` to APIProductReference.Namespace) if you want empty to
mean "use a.Spec.Namespace", or remove the dead fallback logic in the
APIProductKey() method to keep behavior explicit. Locate
APIProductReference.Namespace and APIProductKey() (and the similar code block at
the other occurrence around the 129-142 region) and apply the same change
consistently.

In `@internal/controller/apikey_status_controller.go`:
- Around line 493-497: In enqueueClass, remove the redundant string conversion
around the literal by changing Name: string("apikey-status") to simply Name:
"apikey-status" inside the APIKeyStatusReconciler.enqueueClass function so the
NamespacedName.Name is assigned the literal directly.
- Around line 120-124: The log message in the status-check branch uses a
copy-pasted string "apiproduct status unchanged, skipping update" which is
misleading; update the logger call in the block that computes equalStatus (using
equality.Semantic.DeepEqual(newStatus, apiKey.Status) and compares
apiKey.Generation to apiKey.Status.ObservedGeneration) to reference "apikey"
instead of "apiproduct" (e.g., logger.V(1).Info with a corrected message) so
logs accurately reflect the checked resource.
- Around line 45-49: The top-of-file doc comment is stale: change the comment
that currently reads "APIProductReconciler reconciles a APIProduct object" to
correctly describe the APIKeyStatusReconciler (e.g., "APIKeyStatusReconciler
reconciles an APIKeyStatus object") or remove it; update the comment immediately
preceding the APIKeyStatusReconciler struct declaration so it matches the struct
name APIKeyStatusReconciler and its purpose.
- Around line 478-480: Remove the orphan trailing comment "Clear all status
condition types (Approved, Denied, Failed)" from the file (it sits outside any
function) or, if the intent was to document behavior, move that text immediately
above the function that actually clears status conditions (search for the method
that handles status condition clearing or any function named with "status",
"condition", "Clear", or "Approve/Denie/Fail" in apikey_status_controller.go) so
it becomes a proper doc comment for that function.

In `@internal/controller/apikeyapproval_controller_test.go`:
- Line 59: The test creates a package-level/static context with `ctx :=
context.Background()` which prevents per-spec cancellation and timeouts; remove
that global Background context and instead use the SpecContext provided to each
spec (the `ctx` parameter passed into BeforeEach/AfterEach/It) when calling
k8sClient methods and constructing resources (e.g., replace uses of the global
`ctx` in k8sClient.Create/Delete/Update calls and any container/resource
construction with the per-spec `ctx` variable passed into those hooks).

In `@internal/controller/apikeyapproval_controller.go`:
- Around line 112-124: The current loop over approval.OwnerReferences only
compares UID (approval.OwnerReferences and foundRequest.UID) and can treat
non-controller ownerRefs as controllers; replace that manual UID check with the
helper metav1.IsControlledBy(approval, foundRequest) to explicitly test
controller ownership before returning, and ensure you import metav1
("k8s.io/apimachinery/pkg/apis/meta/v1"); keep the subsequent
controllerutil.SetControllerReference(foundRequest, approval, r.Scheme) logic
unchanged so SetControllerReference still runs when no controller owner is
present.

In `@internal/controller/apikeyapproval_status_controller.go`:
- Around line 198-202: The Name field is using a redundant string(...)
conversion in enqueueClass; change Name: string("apikeyapproval-status") to
Name: "apikeyapproval-status" (remove the unnecessary string() cast) and apply
the same tidy-up to the identical pattern found in the related enqueueClass
implementation in the APIKeyApproval reconciler to remove the no-op conversions.
- Around line 77-87: The loop over activeAPIKeyApprovalList should not return on
the first non-conflict error; instead, continue processing remaining approvals
while aggregating errors: call reconcileStatus(ctx,
&activeAPIKeyApprovalList[idx]) for each item, log failures including the
approval key (use the same key format as apikeyapproval_controller.go), skip
apierrors.IsConflict by requeueing that case, and collect other errors into a
single aggregate (using errors.Join or kerrors.NewAggregate) to return after the
loop so other approvals are reconciled in this pass; update the logger call to
include the offending approval's key and any error details.

In `@internal/controller/devportal_helper.go`:
- Around line 10-16: The constant name apiKeyRequestCtxKey is inconsistent with
its type apiKeyRequestsCtxKeyType; rename the constant to apiKeyRequestsCtxKey
and update all usages to match so the pattern aligns with
apiKeysCtxKey/apiKeysCtxKeyType and
apiKeyApprovalsCtxKey/apiKeyApprovalsCtxKeyType; ensure you update references in
functions or middleware that read/write context keys (look for
apiKeyRequestCtxKey occurrences) to the new apiKeyRequestsCtxKey identifier.

In `@README.md`:
- Around line 400-444: The legend currently labels both cross-namespace and
local references as "solid arrows", which is ambiguous; update the Mermaid
diagram legend or the diagram itself so they are distinguishable: either reword
the bullets to "Cross-Namespace References (solid arrows)" and "Local References
(solid arrows — same-namespace)" or change the local-reference arrow style in
the diagram (the edges APIKey --> APIProduct and APIKeyRequest --> APIProduct
for cross-namespace vs APIKeyRequest --> APIProduct and APIKeyApproval -->
APIKeyRequest for local) to a visually different stroke (e.g., use a different
arrow style or class) and reflect that change in the legend so readers can
clearly tell cross-namespace vs local links.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6a226ad-c150-437b-a19e-c62f6a5ace20

📥 Commits

Reviewing files that changed from the base of the PR and between f8701e7 and 9f85ac1.

📒 Files selected for processing (21)
  • CLAUDE.md
  • CLAUDE.md
  • README.md
  • api/v1alpha1/apikey_types.go
  • api/v1alpha1/apikeyapproval_types.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • cmd/main.go
  • config/crd/bases/devportal.kuadrant.io_apikeyapprovals.yaml
  • config/crd/bases/devportal.kuadrant.io_apikeys.yaml
  • config/rbac/role.yaml
  • internal/controller/apikey_status_controller.go
  • internal/controller/apikey_status_controller_test.go
  • internal/controller/apikeyapproval_controller.go
  • internal/controller/apikeyapproval_controller_test.go
  • internal/controller/apikeyapproval_status_controller.go
  • internal/controller/apikeyapproval_status_controller_test.go
  • internal/controller/apikeyrequest_controller.go
  • internal/controller/apikeyrequest_controller_test.go
  • internal/controller/apikeyrequest_status_controller_test.go
  • internal/controller/devportal_helper.go
  • internal/controller/suite_test.go

Comment thread internal/controller/apikey_status_controller.go Outdated
Comment thread internal/controller/apikeyapproval_status_controller_test.go Outdated
Comment thread internal/controller/apikeyapproval_status_controller.go Outdated
Comment thread internal/controller/apikeyapproval_status_controller.go Outdated
Comment thread internal/controller/apikeyapproval_status_controller.go Outdated
Comment thread internal/controller/devportal_helper.go
Base automatically changed from APIKeyApproval-crd to developer-portal-rbac April 20, 2026 16:42
eguzki added 2 commits April 20, 2026 19:03
Signed-off-by: Eguzki Astiz Lezaun <eastizle@redhat.com>
Signed-off-by: Eguzki Astiz Lezaun <eastizle@redhat.com>
@eguzki eguzki force-pushed the apikey-status-controller branch from 9f85ac1 to 9776ee3 Compare April 20, 2026 17:03
Signed-off-by: Eguzki Astiz Lezaun <eastizle@redhat.com>
@eguzki eguzki marked this pull request as ready for review April 20, 2026 19:37
eguzki added 3 commits April 21, 2026 11:00
Signed-off-by: Eguzki Astiz Lezaun <eastizle@redhat.com>
Signed-off-by: Eguzki Astiz Lezaun <eastizle@redhat.com>
Signed-off-by: Eguzki Astiz Lezaun <eastizle@redhat.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Update controller RBAC permissions and documentation Implement APIKeyApproval CRD and approval workflow controller

3 participants