Skip to content

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

@R-Lawton

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

    enhancementNew feature or request

    Projects

    Status

    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions