summaryrefslogtreecommitdiffstats
path: root/ipaserver/install/ldapupdate.py
diff options
context:
space:
mode:
authorRob Crittenden <rcritten@redhat.com>2011-11-23 16:52:40 -0500
committerRob Crittenden <rcritten@redhat.com>2011-11-22 23:57:10 -0500
commit2f4b3972a04e3ebf99ea7fd51c2b102cc8342582 (patch)
treee2dcc0f790fd56b4067b4f8f50ee7756a2e87e41 /ipaserver/install/ldapupdate.py
parent56401c1abe7d4c78650acfcd9bbe8c8edc1dac57 (diff)
downloadfreeipa-2f4b3972a04e3ebf99ea7fd51c2b102cc8342582.tar.gz
freeipa-2f4b3972a04e3ebf99ea7fd51c2b102cc8342582.tar.xz
freeipa-2f4b3972a04e3ebf99ea7fd51c2b102cc8342582.zip
Add plugin framework to LDAP updates.
There are two reasons for the plugin framework: 1. To provide a way of doing manual/complex LDAP changes without having to keep extending ldapupdate.py (like we did with managed entries). 2. Allows for better control of restarts. There are two types of plugins, preop and postop. A preop plugin runs before any file-based updates are loaded. A postop plugin runs after all file-based updates are applied. A preop plugin may update LDAP directly or craft update entries to be applied with the file-based updates. Either a preop or postop plugin may attempt to restart the dirsrv instance. The instance is only restartable if ipa-ldap-updater is being executed as root. A warning is printed if a restart is requested for a non-root user. Plugins are not executed by default. This is so we can use ldapupdate to apply simple updates in commands like ipa-nis-manage. https://fedorahosted.org/freeipa/ticket/1789 https://fedorahosted.org/freeipa/ticket/1790 https://fedorahosted.org/freeipa/ticket/2032
Diffstat (limited to 'ipaserver/install/ldapupdate.py')
-rw-r--r--ipaserver/install/ldapupdate.py144
1 files changed, 82 insertions, 62 deletions
diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py
index 34637c1ee..c396dab6b 100644
--- a/ipaserver/install/ldapupdate.py
+++ b/ipaserver/install/ldapupdate.py
@@ -31,6 +31,7 @@ from ipaserver import ipaldap
from ipapython import entity, ipautil
from ipalib import util
from ipalib import errors
+from ipalib import api
import ldap
from ldap.dn import escape_dn_chars
from ipapython.ipa_log_manager import *
@@ -42,6 +43,8 @@ import os
import pwd
import fnmatch
import csv
+from ipaserver.install.plugins import PRE_UPDATE, POST_UPDATE
+from ipaserver.install.plugins import FIRST, MIDDLE, LAST
class BadSyntax(Exception):
def __init__(self, value):
@@ -49,32 +52,15 @@ class BadSyntax(Exception):
def __str__(self):
return repr(self.value)
-class IPARestart(service.Service):
- """
- Restart the 389 DS service prior to performing deletions.
- """
- def __init__(self, live_run=True):
- """
- This class is present to provide ldapupdate the means to
- restart 389 DS to apply updates prior to performing deletes.
- """
-
- service.Service.__init__(self, "dirsrv")
- self.live_run = live_run
-
- def create_instance(self):
- self.step("stopping directory server", self.stop)
- self.step("starting directory server", self.start)
- self.start_creation("Restarting IPA to initialize updates before performing deletes:")
-
class LDAPUpdate:
def __init__(self, dm_password, sub_dict={}, live_run=True,
- online=True, ldapi=False):
+ online=True, ldapi=False, plugins=False):
"""dm_password = Directory Manager password
sub_dict = substitution dictionary
live_run = Apply the changes or just test
online = do an online LDAP update or use an experimental LDIF updater
ldapi = bind using ldapi. This assumes autobind is enabled.
+ plugins = execute the pre/post update plugins
"""
self.sub_dict = sub_dict
self.live_run = live_run
@@ -83,6 +69,7 @@ class LDAPUpdate:
self.modified = False
self.online = online
self.ldapi = ldapi
+ self.plugins = plugins
self.pw_name = pwd.getpwuid(os.geteuid()).pw_name
if sub_dict.get("REALM"):
@@ -554,11 +541,11 @@ class LDAPUpdate:
# skip this update type, it occurs in __delete_entries()
return None
elif utype == 'replace':
- # v has the format "old:: new"
+ # v has the format "old::new"
try:
(old, new) = v.split('::', 1)
except ValueError:
- raise BadSyntax, "bad syntax in replace, needs to be in the format old: new in %s" % v
+ raise BadSyntax, "bad syntax in replace, needs to be in the format old::new in %s" % v
try:
e.remove(old)
e.append(new)
@@ -708,11 +695,12 @@ class LDAPUpdate:
deletes = updates.get('deleteentry', [])
for d in deletes:
try:
- if self.live_run:
- self.conn.deleteEntry(dn)
- self.modified = True
+ root_logger.info('Deleting entry %s", dn)
+ if self.live_run:
+ self.conn.deleteEntry(dn)
+ self.modified = True
except errors.NotFound, e:
- root_logger.info("Deleting non-existent entry %s", e)
+ root_logger.info("%s did not exist:%s", (dn, e))
self.modified = True
except errors.DatabaseError, e:
root_logger.error("Delete failed: %s", e)
@@ -724,11 +712,12 @@ class LDAPUpdate:
if utype == 'deleteentry':
try:
- if self.live_run:
- self.conn.deleteEntry(dn)
- self.modified = True
+ root_logger.info('Deleting entry %s", dn)
+ if self.live_run:
+ self.conn.deleteEntry(dn)
+ self.modified = True
except errors.NotFound, e:
- root_logger.info("Deleting non-existent entry %s", e)
+ root_logger.info("%s did not exist:%s", (dn, e))
self.modified = True
except errors.DatabaseError, e:
root_logger.error("Delete failed: %s", e)
@@ -772,16 +761,49 @@ class LDAPUpdate:
else:
raise RuntimeError("Offline updates are not supported.")
+ def __run_updates(self, dn_list, all_updates):
+ # For adds and updates we want to apply updates from shortest
+ # to greatest length of the DN. For deletes we want the reverse.
+ sortedkeys = dn_list.keys()
+ sortedkeys.sort()
+ for k in sortedkeys:
+ for dn in dn_list[k]:
+ self.__update_record(all_updates[dn])
+
+ sortedkeys.reverse()
+ for k in sortedkeys:
+ for dn in dn_list[k]:
+ self.__delete_record(all_updates[dn])
+
def update(self, files):
"""Execute the update. files is a list of the update files to use.
returns True if anything was changed, otherwise False
"""
+ updates = None
+ if self.plugins:
+ logging.info('PRE_UPDATE')
+ updates = api.Backend.updateclient.update(PRE_UPDATE, self.dm_password, self.ldapi, self.live_run)
+
try:
self.create_connection()
all_updates = {}
dn_list = {}
+ # Start with any updates passed in from pre-update plugins
+ if updates:
+ for entry in updates:
+ all_updates.update(entry)
+ for upd in updates:
+ for dn in upd:
+ dn_explode = ldap.explode_dn(dn.lower())
+ l = len(dn_explode)
+ if dn_list.get(l):
+ if dn not in dn_list[l]:
+ dn_list[l].append(dn)
+ else:
+ dn_list[l] = [dn]
+
for f in files:
try:
root_logger.info("Parsing file %s" % f)
@@ -792,40 +814,38 @@ class LDAPUpdate:
(all_updates, dn_list) = self.parse_update_file(data, all_updates, dn_list)
- # Process Managed Entry Updates
- managed_entries = self.__update_managed_entries()
- if managed_entries:
- managed_entry_dns = [[m[entry]['dn'] for entry in m] for m in managed_entries]
- l = len(dn_list.keys())
-
- # Add Managed Entry DN's to the DN List
- for dn in managed_entry_dns:
- l+=1
- dn_list[l] = dn
- # Add Managed Entry Updates to All Updates List
- for managed_entry in managed_entries:
- all_updates.update(managed_entry)
-
- # For adds and updates we want to apply updates from shortest
- # to greatest length of the DN. For deletes we want the reverse.
- sortedkeys = dn_list.keys()
- sortedkeys.sort()
- for k in sortedkeys:
- for dn in dn_list[k]:
- self.__update_record(all_updates[dn])
-
- # Restart 389 Directory Service
- socket_name = '/var/run/slapd-%s.socket' % self.realm.replace('.','-')
- iparestart = IPARestart()
- iparestart.create_instance()
- installutils.wait_for_open_socket(socket_name)
- self.create_connection()
-
- sortedkeys.reverse()
- for k in sortedkeys:
- for dn in dn_list[k]:
- self.__delete_record(all_updates[dn])
+ self.__run_updates(dn_list, all_updates)
finally:
if self.conn: self.conn.unbind()
+ if self.plugins:
+ logging.info('POST_UPDATE')
+ updates = api.Backend.updateclient.update(POST_UPDATE, self.dm_password, self.ldapi, self.live_run)
+ dn_list = {}
+ for upd in updates:
+ for dn in upd:
+ dn_explode = ldap.explode_dn(dn.lower())
+ l = len(dn_explode)
+ if dn_list.get(l):
+ if dn not in dn_list[l]:
+ dn_list[l].append(dn)
+ else:
+ dn_list[l] = [dn]
+ self.__run_updates(dn_list, updates)
+
+ return self.modified
+
+
+ def update_from_dict(self, dn_list, updates):
+ """
+ Apply updates internally as opposed to from a file.
+
+ dn_list is a list of dns to be updated
+ updates is a dictionary containing the updates
+ """
+ if not self.conn:
+ self.create_connection()
+
+ self.__run_updates(dn_list, updates)
+
return self.modified