Skip to content

APIProduct lifecycle enhancements: Deprecated/Retired states and deletion finalizers #38

Description

@R-Lawton

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:

// 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:

// +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 <date>"
  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)
  • 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:

// 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


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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions