Skip to content

User Reconciler #11

Description

@lukidoescode

Motivation

The user reconciler is the core business logic that synchronizes LldapUser CRDs with actual lldap user records.

Definition of Done

  • On Apply (create or update):
    1. Read the LldapUser spec.
    2. Check if the user exists in lldap (query by username).
    3. If not exists: call createUser mutation. If createUser returns "already exists" (e.g., operator crashed between create and status update), treat as exists and proceed to update path. Set password per policy.
    4. If exists: compare spec fields against current lldap state. Call updateUser only if there are differences.
    5. Handle password per passwordPolicy:
      • Manage: read Secret, hash and compare against status.passwordHash. If changed, execute OPAQUE registration. Update status.passwordHash.
      • InitialOnly: set password only on first creation (user doesn't exist yet in lldap). Never update afterwards.
      • Ignore: skip password handling entirely.
    6. If the user spec references custom attributes, verify that each referenced attribute has a corresponding LldapAttributeSchema CRD in Ready state. If not, set DependencyNotReady condition and requeue.
    7. Update status: set uuid, observedGeneration, conditions to Ready=True.
  • On Cleanup (CRD deleted, finalizer triggered):
    1. Delete user from lldap via deleteUser(userId).
    2. If lldap returns "not found", treat as success (idempotent).
    3. Remove finalizer.
  • Error handling:
    • Transient errors (network, 5xx): requeue with exponential backoff.
    • Permanent errors (invalid spec, missing Secret): set Ready=False with reason, requeue with longer interval.
    • Secret not found: set condition SecretNotFound, requeue after 30s.
  • The reconciler is idempotent: calling it multiple times with unchanged state produces no side effects.
  • Emit Kubernetes events for significant state changes (user created, updated, deleted, error).
  • Documentation: Update the CRD Reference with a detailed LldapUser usage guide. Document the password management policies in the Password Management chapter. Add a complete worked example to docs/examples/.

Technical Details

  • Use Server-Side Apply for all status patches (see Ticket Reconciliation Robustness and Startup Behavior #15).
  • Finalizer name: lldap-operator.lukidoescode.com/user-cleanup
  • Use kube::runtime::finalizer::finalizer() helper.
  • Password hash stored in status: SHA-256 of the Secret value. This enables detecting Secret changes without storing the password itself.
  • On restart: the reconciler is triggered for all existing CRDs. Since we use push-on-CRD-change semantics (observedGeneration check), if status.observedGeneration == metadata.generation, the reconciler short- circuits spec-field processing. This prevents unnecessary API calls and avoids thrashing lldap on restart.
  • Hash-based bypass for password handling: The observedGeneration check only short-circuits spec-field processing. Password handling ALWAYS runs its own comparison: read the Secret, compute SHA-256, compare against status.passwordHash. If they differ, apply the password via OPAQUE regardless of whether metadata.generation changed. This is necessary because Secret changes do not increment the CRD's generation, but the Secret watch (Ticket Secret Watching and Password Change Detection #12) enqueues the LldapUser for reconciliation.
  • Watch Secrets: use Controller::watches() on Secrets. The mapper function finds LldapUsers in the same namespace that reference the changed Secret and enqueues them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions