You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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):
Read the LldapUser spec.
Check if the user exists in lldap (query by username).
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.
If exists: compare spec fields against current lldap state. Call updateUser only if there are differences.
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.
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.
Update status: set uuid, observedGeneration, conditions to Ready=True.
On Cleanup (CRD deleted, finalizer triggered):
Delete user from lldap via deleteUser(userId).
If lldap returns "not found", treat as success (idempotent).
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/.
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.
Motivation
The user reconciler is the core business logic that synchronizes LldapUser CRDs with actual lldap user records.
Definition of Done
Apply(create or update):createUsermutation. IfcreateUserreturns "already exists" (e.g., operator crashed between create and status update), treat as exists and proceed to update path. Set password per policy.updateUseronly if there are differences.passwordPolicy:Manage: read Secret, hash and compare againststatus.passwordHash. If changed, execute OPAQUE registration. Updatestatus.passwordHash.InitialOnly: set password only on first creation (user doesn't exist yet in lldap). Never update afterwards.Ignore: skip password handling entirely.DependencyNotReadycondition and requeue.uuid,observedGeneration, conditions to Ready=True.Cleanup(CRD deleted, finalizer triggered):deleteUser(userId).SecretNotFound, requeue after 30s.docs/examples/.Technical Details
lldap-operator.lukidoescode.com/user-cleanupkube::runtime::finalizer::finalizer()helper.status.observedGeneration == metadata.generation, the reconciler short- circuits spec-field processing. This prevents unnecessary API calls and avoids thrashing lldap on restart.status.passwordHash. If they differ, apply the password via OPAQUE regardless of whethermetadata.generationchanged. 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.Controller::watches()on Secrets. The mapper function finds LldapUsers in the same namespace that reference the changed Secret and enqueues them.