Directory Migration as a Workflow
Major-version directory server upgrades keep failing for the same avoidable reasons. Treated as a repeatable four-stage workflow, they stop being an event and become routine engineering.
The problem with directory upgrades
A major-version directory server upgrade is one of the few pieces of infrastructure work where “it looked fine” is not a defensible outcome. Directory services sit underneath authentication, authorisation, mail routing, and application configuration for everything downstream. When a migration goes wrong, it goes wrong quietly: a stale replica keeps answering binds, a schema extension does not carry across, an access control instruction silently drops, and nobody notices until a help desk queue fills up two weeks later.
Most teams still treat these upgrades as bespoke projects — a runbook written once, executed by hand, and half-remembered by the time the next major version ships. We treat them instead as a workflow: install, export/import, replication, verify. Four stages, each with its own Ansible role, each idempotent, each producing evidence rather than a shrug.
A four-stage workflow, not a one-off runbook
Install stands up the target instance — new binaries, new instance layout, TLS material, and the schema baseline — without touching production data. This stage is pure infrastructure-as-code: the same role runs against a lab instance, a staging instance, and production, with only inventory variables changing.
Export/import moves data using the vendor’s supported LDIF path rather than a database-file copy, because a raw copy carries forward on-disk format assumptions that a major version bump is often specifically changing. We export with db2ldif (or the equivalent for the platform in use), validate the LDIF against a schema check on the target before import, and stage the import as its own task with explicit exit-code handling — an import that emits warnings is not treated as a success by default.
Replication is rebuilt, not carried over. Agreements, replica IDs, and change-log configuration are recreated against the new topology rather than assumed to migrate cleanly, because replication metadata is exactly the kind of state that a version upgrade is most likely to invalidate silently.
Verify is the stage most teams shortchange, and it is the one we spend the most engineering effort on.
Verification must fail loudly
A migration that “completes” without an independent verification pass has not actually finished — it has stopped. Our verify role checks entry counts per suffix against a pre-migration baseline, diffs a sample of full entries (not just RDNs) between source and target, confirms every ACI and password policy object survived the import, and checks replication status across all agreements until they report in sync, not merely “started.”
The design decision that matters most here is that verification failures must be loud. A soft warning in a log file that scrolls past on a terminal is not a control — it is theatre. Our Ansible roles use assert and fail modules deliberately, so that a discrepancy stops the play, returns a non-zero exit code, and surfaces in whatever pipeline invoked it, rather than allowing the run to report green with a caveat buried in -vvv output.
# Single-suffix illustration; the role invokes this once per suffix
# enumerated in inventory (see the multi-suffix section below).
- name: Verify entry counts match baseline
ansible.builtin.command: >
ldapsearch -x -D "cn=Directory Manager" -y {{ ds_bind_pw_file }}
-H "ldaps://{{ ds_host }}"
-b "{{ suffix_dn }}" -s sub "(objectclass=*)"
register: count_result
changed_when: false
no_log: true
- name: Fail loudly on count mismatch
ansible.builtin.assert:
that:
- "count_result.stdout_lines[-1] | regex_search('numEntries: ' + baseline_count|string)"
fail_msg: "Entry count mismatch on suffix {{ suffix_dn }} — migration NOT verified."
A migration is not done because the play finished. It is done because verify passed and said so.
Multi-instance, multi-suffix topologies change the arithmetic
Enterprise directory estates are rarely one instance with one suffix. It is common to find several instances per site serving distinct application populations, each with its own suffix, its own ACI set, and its own replication fan-out. A workflow that only handles the single-instance case will pass every test in the lab and then fail on the first production estate it meets.
We parameterise the entire workflow over an inventory structure that enumerates instance, suffix, and replica-agreement combinations, and we run install/export-import/replication/verify per suffix, not per host. A host running three instances with five suffixes between them produces five verification reports, not one. This is more work upfront and considerably less work at 2 a.m. when only one suffix out of five has a problem and the runbook needs to say precisely which one.
Credentials never touch the log
Every stage above involves binding to a directory with elevated credentials, and Ansible’s default verbosity will happily print them to a log file, a CI console, or a ticketing system attachment. We treat no_log: true on any task touching a bind password, API token, or vault-decrypted variable as non-negotiable, not situational — including on tasks that “shouldn’t” leak anything, because the failure mode we are guarding against is exactly the case where a module unexpectedly echoes its full parameter set on error. The verify task above binds with real credentials for exactly this reason: an anonymous bind against an ACI-protected suffix would return a different, unrepresentative slice of the tree and verify nothing.
Credentials live in Ansible Vault or an external secrets manager, are referenced by lookup rather than interpolated into command strings, and are rotated as a matter of course once a migration project closes, on the assumption that they passed through more hands and more terminals than the access model strictly required.
None of this is exotic. It is the difference between a migration that depends on the competence of whoever is running it that week, and one that depends on a workflow the firm has already proven.