Kuadrant / Kuadrant/developer-portal-controller
APIProduct lifecycle enhancements: Deprecated/Retired states and deletion finalizers
- Dominant language
- Go
- Stars
- 2
- Forks
- 12
- Avg merge
- 13h 37m
- Merged PRs (30d)
- 5
Description
## Summary
The console-plugin's Developer Portal integration requires two enhancements to the APIProduct CRD and controller logic:
1. **Extend lifecycle states** to support full API retirement workflow (Draft → Published → Deprecated → Retired)
2. **Add finalizer** for clean cascade deletion of APIProducts with dependent APIKeys
These are blocking console-plugin issues #320 and #321.
---
## 1. Extend `publishStatus` Enum for Lifecycle States
### Current State
The `PublishStatus` field only supports two states:
```go
// api/v1alpha1/apiproduct_types.go:102-104
// +kubebuilder:validation:Enum=Draft;Published
// +kubebuilder:default=Draft
PublishStatus string `json:"publishStatus"`
```
### Required Change
Extend to support four lifecycle states:
```go
// +kubebuilder:validation:Enum=Draft;Published;Deprecated;Retired
// +kubebuilder:default=Draft
PublishStatus string `json:"publishStatus"`
```
### State Definitions
- **Draft**: Not visible in API catalog, cannot request keys
- **Published**: Visible in catalog, accepting new requests
- **Deprecated**: Visible with warning badge, new APIKey requests blocked, existing approved keys continue working (grace period)
- **Retired**: Not visible in catalog, all existing APIKeys transitioned to Rejected phase, Secrets deleted
### Controller Logic Required
When APIProduct transitions to `publishStatus: Retired`:
1. Query all APIKey resources where `spec.apiProductRef.name` matches the APIProduct
2. For each APIKey:
- If `status.phase: Approved` → update to `Rejected`, delete the Secret
- If `status.phase: Pending` → update to `Rejected`
- Set `status.rejectionReason: "Parent API Product was retired on "`
3. Update APIProduct status conditions to reflect retirement
**Validation:** Block new APIKey creation if `publishStatus: Retired` (validating webhook)
---
## 2. Finalizer for Clean Cascade Deletion
### Current State
- Controller has RBAC for finalizers: `apiproducts/finalizers,verbs=update`
- Controller sets `OwnerReference` on APIKeys ([apikey_controller.go:130-138](https://github.com/Kuadrant/developer-portal-controller/blob/main/internal/controller/apikey_controller.go#L130-L138))
- **Missing:** Finalizer add/remove logic in reconcile loop
### Required Implementation
Add finalizer `devportal.kuadrant.io/apiproduct-finalizer` to ensure clean deletion order:
**Deletion Flow:**
```
1. User deletes APIProduct → resource gets deletionTimestamp
2. Controller detects deletionTimestamp, starts cleanup:
- Delete all child APIKeys (Pending, Approved, Rejected)
- Wait for Kubernetes garbage collection to delete Secrets
3. Once all APIKeys are gone → Controller removes finalizer
4. APIProduct is finally deleted
```
**Why this matters:**
- Prevents orphaned APIKeys and Secrets
- Ensures all API access is revoked before APIProduct removal
- Provides predictable deletion behavior for console-plugin UI
### Console-Plugin UX Dependency
The console-plugin delete modal (issue #321) needs to query dependent APIKeys before deletion:
```jsx
// Query dependent APIKeys
const dependentKeys = apiKeys.filter(
key => key.spec.apiProductRef.name === apiProduct.metadata.name
);
// Show counts in delete confirmation modal
- 47 approved API keys will be revoked immediately
- 12 pending requests will be cancelled
- 8 rejected requests will be deleted
```
The finalizer ensures this cascade deletion completes cleanly.
---
## Acceptance Criteria
- [ ] `PublishStatus` enum extended to include `Deprecated` and `Retired`
- [ ] Controller reconciles `Retired` state by transitioning dependent APIKeys to Rejected
- [ ] Controller deletes Secrets when transitioning approved APIKeys to Rejected
- [ ] Validating webhook blocks new APIKey creation for retired APIProducts
- [ ] Finalizer added to APIProduct on creation
- [ ] Controller removes finalizer only after all dependent APIKeys are deleted
- [ ] Deletion flow tested with 10+ dependent APIKeys (approved/pending/rejected mix)
- [ ] CRD manifests updated and regenerated
- [ ] Documentation updated with lifecycle state definitions
---
## References
- [Console-plugin design doc - Edge Cases #2 (Finalizers)](https://github.com/Kuadrant/console-plugin/blob/main/docs/designs/2026-03-25-dev-portal-integration-design.md#2-apiproduct-deletion-and-cascade-effects)
- [Console-plugin design doc - Edge Cases #3 (Lifecycle States)](https://github.com/Kuadrant/console-plugin/blob/main/docs/designs/2026-03-25-dev-portal-integration-design.md#3-apiproduct-lifecycle-states-draft--published--deprecated--retired)
- [Console-plugin #321 - APIProduct Create/Edit Form](https://github.com/Kuadrant/console-plugin/issues/321)
- [Console-plugin #320 - APIProduct Detail Page](https://github.com/Kuadrant/console-plugin/issues/320)
---
## Notes
### Un-retirement Behavior
If an APIProduct is un-retired (transitioned from `Retired` back to `Published`):
- Previously revoked APIKeys remain in `Rejected` state (Secrets were deleted)
- Users must create new APIKey requests to regain access
- This is by design to prevent accidental re-activation of old credentials
### Testing Recommendations
1. Create APIProduct with 10+ dependent APIKeys (mix of approved/pending/rejected)
2. Transition to `Retired` → verify all approved keys become rejected, Secrets deleted
3. Delete APIProduct → verify finalizer prevents deletion until all APIKeys cleaned up
4. Attempt to create new APIKey for retired product → verify validation blocks it
Contributor guide
Research direction
Start with api/v1alpha1/apiproduct_types.go for the PublishStatus enum and internal/controller/apikey_controller.go for existing APIKey ownership behavior, then trace the APIProduct reconcile path and validation webhook. The work is done when retired products revoke dependent keys and secrets, deletion waits for child cleanup through the finalizer, manifests and documentation are updated, and the recommended mixed-key scenarios pass.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- go, kubernetes
- Domain
- api, backend, infrastructure
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100