From fe31b9d1e56ccb4b9c0da012bcd8dd76ef993178 Mon Sep 17 00:00:00 2001
From: Rich Megginson <rmeggins@redhat.com>
Date: Thu, 19 Dec 2013 14:21:52 -0700
Subject: [PATCH] Ticket #47648 lib389 - add schema classes, methods

https://fedorahosted.org/389/ticket/47648
Reviewed by: ???
Branch: master
Fix Description: Add a schema.py and a Schema class for use as
dirsrv.schema.  It has methods to add/delete schema elements, to get a list
of schema files for an instance, and to get the schema as an LDAP entry or
as a python-ldap SubSchema object.
Platforms tested: RHEL6 x86_64
Flag Day: no
Doc impact: no
---
 lib389/__init__.py   |   27 ++++------------
 lib389/_constants.py |    4 ++-
 lib389/brooker.py    |    1 +
 lib389/schema.py     |   85 ++++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 96 insertions(+), 21 deletions(-)
 create mode 100644 lib389/schema.py

diff --git a/lib389/__init__.py b/lib389/__init__.py
index 60ac2f2..ad57539 100644
--- a/lib389/__init__.py
+++ b/lib389/__init__.py
@@ -205,12 +205,16 @@ class DirSrv(SimpleLDAPObject):
                 self.errlog    = ent.getValue('nsslapd-errorlog')
                 self.accesslog = ent.getValue('nsslapd-accesslog')
                 self.confdir   = ent.getValue('nsslapd-certdir')
-                
+                self.schemadir = ent.getValue('nsslapd-schemadir')
+
                 if self.isLocal:
                     if not self.confdir or not os.access(self.confdir + '/dse.ldif', os.R_OK):
                         self.confdir = ent.getValue('nsslapd-schemadir')
                         if self.confdir:
                             self.confdir = os.path.dirname(self.confdir)
+                if not self.schemadir:
+                    # assume it is in the "schema" subdir of the confdir
+                    self.schemadir = os.path.join(self.confdir, "schema")
                 instdir = ent.getValue('nsslapd-instancedir')
                 if not instdir and self.isLocal:
                     # get instance name from errorlog
@@ -304,6 +308,7 @@ class DirSrv(SimpleLDAPObject):
         from lib389.mappingTree import MappingTree
         from lib389.backend import Backend
         from lib389.suffix import Suffix
+        from lib389.schema import Schema
         
         self.agreement   = Agreement(self)
         self.replica     = Replica(self)
@@ -312,6 +317,7 @@ class DirSrv(SimpleLDAPObject):
         self.index       = Index(self)
         self.mappingtree = MappingTree(self)
         self.suffix      = Suffix(self)
+        self.schema      = Schema(self)
     
     def __init__(self, verbose=False, timeout=10):  
         """
@@ -1521,25 +1527,6 @@ class DirSrv(SimpleLDAPObject):
         replace = [(ldap.MOD_REPLACE, 'nsslapd-require-index', 'on')]
         self.modify_s(dn, replace)
 
-    def addSchema(self, attr, val):
-        dn = "cn=schema"
-        self.modify_s(dn, [(ldap.MOD_ADD, attr, val)])
-        
-    def delSchema(self, attr, val):
-        dn = "cn=schema"
-        self.modify_s(dn, [(ldap.MOD_DELETE, attr, val)])
-
-    def addAttr(self, *attributes):
-        return self.addSchema('attributeTypes', attributes)
-
-    def addObjClass(self, *objectclasses):
-        return self.addSchema('objectClasses', objectclasses)
-    
-    def getSchemaCSN(self):
-        ents = self.search_s("cn=schema", ldap.SCOPE_BASE, "objectclass=*", ['nsSchemaCSN'])
-        ent = ents[0]
-        return ent.getValue('nsSchemaCSN')
-
     def setupChainingIntermediate(self):
         confdn = ','.join(("cn=config", DN_CHAIN))
         try:
diff --git a/lib389/_constants.py b/lib389/_constants.py
index 35fbcf3..d0c29ca 100644
--- a/lib389/_constants.py
+++ b/lib389/_constants.py
@@ -32,7 +32,9 @@ DEFAULT_USER = "nobody"
 
 # Some DN constants
 DN_DM = "cn=Directory Manager"
-
+DN_CONFIG = "cn=config"
+DN_LDBM = "cn=ldbm database,cn=plugins,cn=config"
+DN_SCHEMA = "cn=schema"
 
 CMD_PATH_SETUP_DS = "/setup-ds.pl"
 CMD_PATH_REMOVE_DS = "/remove-ds.pl"
diff --git a/lib389/brooker.py b/lib389/brooker.py
index 3d43009..88a2f7e 100644
--- a/lib389/brooker.py
+++ b/lib389/brooker.py
@@ -11,6 +11,7 @@ import ldap
 import os
 import re
 import time
+import glob
 
 
 from lib389._constants import *
diff --git a/lib389/schema.py b/lib389/schema.py
new file mode 100644
index 0000000..bc17cf5
--- /dev/null
+++ b/lib389/schema.py
@@ -0,0 +1,85 @@
+"""
+   You will access this from:
+   DirSrv.schema.methodName()
+"""
+import ldap
+import os
+import re
+import time
+import glob
+
+
+from lib389._constants import *
+from lib389 import Entry
+from lib389.utils import normalizeDN, escapeDNValue, suffixfilt
+
+class Schema(object):
+    
+    def __init__(self, conn):
+        """@param conn - a DirSrv instance"""
+        self.conn = conn
+        self.log = conn.log
+    
+    def get_entry(self):
+        """get the schema as an LDAP entry"""
+        attrs = ['attributeTypes', 'objectClasses']
+        return self.conn.search_s(DN_SCHEMA, ldap.SCOPE_BASE, 'objectclass=*', attrs)[0]
+
+    def get_subschema(self):
+        """get the schema as a python-ldap SubSchema object"""
+        return ldap.schema.SubSchema(self.get_entry().data)
+
+    def list_files(self):
+        """return a list of the schema files in the instance schemadir"""
+        return glob.glob(self.conn.schemadir + "/*.ldif")
+
+    def file_to_ldap(self, filename):
+        """convert the given schema file name to its python-ldap format
+        suitable for passing to ldap.schema.SubSchema()
+        @param filename - the full path and filename of a schema file in ldif format"""
+        import urllib,ldif
+        ldif_file = urllib.urlopen(filename)
+        ldif_parser = ldif.LDIFRecordList(ldif_file,max_entries=1)
+        if not ldif_parser:
+            return None
+        ldif_parser.parse()
+        if not ldif_parser.all_records:
+            return None
+        return ldif_parser.all_records[0][1]
+
+    def file_to_subschema(self, filename):
+        """convert the given schema file name to its python-ldap format
+        ldap.schema.SubSchema object
+        @param filename - the full path and filename of a schema file in ldif format"""
+        ent = self.file_to_ldap(filename)
+        if not ent:
+            return None
+        return ldap.schema.SubSchema(ent)
+
+    def add_schema(self, attr, val):
+        """Add a schema element to the schema.
+        @param attr the attribute type to use e.g. attributeTypes or objectClasses
+        @param val the schema element definition to add"""
+        self.conn.modify_s(DN_SCHEMA, [(ldap.MOD_ADD, attr, val)])
+        
+    def del_schema(self, attr, val):
+        """Delete a schema element from the schema.
+        @param attr the attribute type to use e.g. attributeTypes or objectClasses
+        @param val the schema element definition to delete"""
+        self.conn.modify_s(DN_SCHEMA, [(ldap.MOD_DELETE, attr, val)])
+
+    def add_attribute(self, *attributes):
+        """Add an attribute type definition to the schema.
+        @param attributes a single or list of attribute type defintions to add"""
+        return self.add_schema('attributeTypes', attributes)
+
+    def add_objectclass(self, *objectclasses):
+        """Add an object class definition to the schema.
+        @param objectclasses a single or list of object class defintions to add"""
+        return self.add_schema('objectClasses', objectclasses)
+    
+    def get_schema_csn(self):
+        """return the schema nsSchemaCSN attribute"""
+        ents = self.conn.search_s(DN_SCHEMA, ldap.SCOPE_BASE, "objectclass=*", ['nsSchemaCSN'])
+        ent = ents[0]
+        return ent.getValue('nsSchemaCSN')
-- 
1.7.1

