From 007118ee71651e1ba3af0ff502c0351a51111c2d Mon Sep 17 00:00:00 2001
From: William Brown <wililam@adelaide.edu.au>
Date: Thu, 6 Aug 2015 10:11:25 +0930
Subject: [PATCH] Example of commandline tools implementation for listing all
 attribute types and instances

---
 clitools/__init__.py                      | 43 +++++++++++++++++++++++
 clitools/ds_list_instances.py             | 18 ++++++++++
 clitools/ds_schema_attributetype_list.py  | 24 +++++++++++++
 clitools/ds_schema_attributetype_query.py | 31 +++++++++++++++++
 lib389/__init__.py                        | 58 ++++++++++++++++++++++++++-----
 lib389/_constants.py                      | 15 ++++++++
 6 files changed, 180 insertions(+), 9 deletions(-)
 create mode 100644 clitools/__init__.py
 create mode 100644 clitools/ds_list_instances.py
 create mode 100644 clitools/ds_schema_attributetype_list.py
 create mode 100644 clitools/ds_schema_attributetype_query.py

diff --git a/clitools/__init__.py b/clitools/__init__.py
new file mode 100644
index 0000000..ecc708f
--- /dev/null
+++ b/clitools/__init__.py
@@ -0,0 +1,43 @@
+# Probably need some helpers for argparse and instance selection
+
+from argparse import ArgumentParser
+from getpass import getpass
+from lib389 import DirSrv
+from lib389._constants import *
+
+class CliTool(object):
+    def __init__(self, args=None):
+        self.ds = DirSrv()
+        if args is not None:
+            self.args = args
+
+    def populate_instance_dict(self, instance):
+        insts = self.ds.list(serverid=instance)
+        if len(insts) != 1:
+            # Raise an exception here?
+            self.inst = None
+        else:
+            self.inst = insts[0]
+
+    def get_rootdn_pass(self):
+        #There is a dict get key thing somewhere ...
+        if self.inst.get(SER_ROOT_PW, None) is None:
+            self.inst[SER_ROOT_PW] = getpass('Enter password for %s on instance %s: ' % (self.inst[SER_ROOT_DN], self.inst[SER_SERVERID_PROP]))
+        return
+
+    def connect(self):
+        self.get_rootdn_pass()
+        self.ds.allocate(self.inst)
+        self.ds.open()
+
+    def disconnect(self):
+        # Is there a ds unbind / disconnect?
+        self.ds.close()
+
+def _clitools_parser():
+    parser = ArgumentParser(add_help=False)
+    parser.add_argument('--instance', '-i', help='The name of the DS instance to connect to and work upon.')
+    return parser
+
+clitools_parser = _clitools_parser()
+
diff --git a/clitools/ds_list_instances.py b/clitools/ds_list_instances.py
new file mode 100644
index 0000000..8647187
--- /dev/null
+++ b/clitools/ds_list_instances.py
@@ -0,0 +1,18 @@
+#!/usr/bin/python
+
+from lib389._constants import *
+from clitools import CliTool
+
+class ListTool(CliTool):
+    def list_instances(self):
+        # Remember, the prefix can be set with the os environment
+        instances = self.ds.list(all=True)
+        print('Instances on this system:')
+        for instance in instances:
+            print(instance[CONF_SERVER_ID])
+
+if __name__ == '__main__':
+    listtool = ListTool()
+    listtool.list_instances()
+
+
diff --git a/clitools/ds_schema_attributetype_list.py b/clitools/ds_schema_attributetype_list.py
new file mode 100644
index 0000000..0239ac3
--- /dev/null
+++ b/clitools/ds_schema_attributetype_list.py
@@ -0,0 +1,24 @@
+#!/usr/bin/python
+
+#from clitools import clitools_parser, get_instance_dict, get_rootdn_pass
+from clitools import CliTool, clitools_parser
+#from lib389 import DirSrv
+from lib389._constants import *
+import ldap
+
+class SchemaTool(CliTool):
+    def schema_attributetype_list(self):
+        try:
+            self.populate_instance_dict(self.args.instance)
+            self.connect()
+            for attributetype in self.ds.schema.get_attributetypes():
+                print(attributetype)
+        finally:
+            self.disconnect()
+
+if __name__ == '__main__':
+    # Do some arg parse stuff
+    ## You can always add a child parser here too ...
+    args = clitools_parser.parse_args()
+    schematool = SchemaTool(args)
+    schematool.schema_attributetype_list()
diff --git a/clitools/ds_schema_attributetype_query.py b/clitools/ds_schema_attributetype_query.py
new file mode 100644
index 0000000..b390418
--- /dev/null
+++ b/clitools/ds_schema_attributetype_query.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+
+from clitools import CliTool, clitools_parser
+from lib389._constants import *
+import ldap
+from argparse import ArgumentParser
+
+class SchemaTool(CliTool):
+    def schema_attributetype_query(self):
+        try:
+            self.populate_instance_dict(self.args.instance)
+            self.connect()
+            attributetype, must, may = self.ds.schema.query_attributetype(self.args.attributetype)
+            print(attributetype)
+            print('MUST')
+            for objectclass in must:
+                print(objectclass)
+            print('MAY')
+            for objectclass in may:
+                print(objectclass)
+        finally:
+            self.disconnect()
+
+if __name__ == '__main__':
+    # Do some arg parse stuff
+    ## You can always add a child parser here too ...
+    parser = ArgumentParser(parents=[clitools_parser])
+    parser.add_argument('--attributetype', '-a', help='The name of the attribute type to query', required=True)
+    args = parser.parse_args()
+    schematool = SchemaTool(args)
+    schematool.schema_attributetype_query()
diff --git a/lib389/__init__.py b/lib389/__init__.py
index 10bf0df..a516992 100644
--- a/lib389/__init__.py
+++ b/lib389/__init__.py
@@ -374,7 +374,7 @@ class DirSrv(SimpleLDAPObject):
         self.timeout = timeout
 
         # Reset the args (py.test reuses the args_instance for each test case)
-        args_instance[SER_DEPLOYED_DIR] = os.environ.get('PREFIX', None)
+        args_instance[SER_DEPLOYED_DIR] = os.environ.get('PREFIX', '/')
         args_instance[SER_BACKUP_INST_DIR] = os.environ.get('BACKUPDIR', DEFAULT_BACKUPDIR)
         args_instance[SER_ROOT_DN] = DN_DM
         args_instance[SER_ROOT_PW] = PW_DM
@@ -386,6 +386,12 @@ class DirSrv(SimpleLDAPObject):
         args_instance[SER_USER_ID] = None
         args_instance[SER_GROUP_ID] = None
 
+        # We allocate a "default" prefix here which allows an un-allocate or un-instantiated DirSrv
+        # instance to be able to do an an instance discovery. For example:
+        #  ds = lib389.DirSrv()
+        #  ds.list(all=True)
+        self.prefix = args_instance[SER_DEPLOYED_DIR]
+
         self.__wrapmethods()
         self.__add_brookers__()
 
@@ -440,7 +446,7 @@ class DirSrv(SimpleLDAPObject):
         self.serverid  = args.get(SER_SERVERID_PROP, None)
         self.groupid   = args.get(SER_GROUP_ID, self.userid)
         self.backupdir = args.get(SER_BACKUP_INST_DIR, DEFAULT_BACKUPDIR)
-        self.prefix    = args.get(SER_DEPLOYED_DIR) or '/'
+        self.prefix    = args.get(SER_DEPLOYED_DIR)
 
         # Those variables needs to be revisited (sroot for 64 bits)
         #self.sroot     = os.path.join(self.prefix, "lib/dirsrv")
@@ -471,7 +477,7 @@ class DirSrv(SimpleLDAPObject):
 
         return server
 
-    def list(self, all=False):
+    def list(self, all=False, serverid=None):
         """
             Returns a list dictionary. For a created instance that is on the local file system
             (e.g. <prefix>/etc/dirsrv/slapd-*), it exists a file describing its properties
@@ -489,6 +495,8 @@ class DirSrv(SimpleLDAPObject):
 
             @param all - True or False . default is [False]
 
+            @param instance - The name of the instance to retrieve or None for the current instance
+
             @return list of dictionaries, each of them containing instance properities
 
             @raise IOError - if the file containing the properties is not foundable or readable
@@ -503,7 +511,7 @@ class DirSrv(SimpleLDAPObject):
                 return 1
             return 0
 
-        def _parse_configfile(filename=None):
+        def _parse_configfile(filename=None, serverid=None):
             '''
                 This method read 'filename' and build a dictionary with
                 CONF_* properties
@@ -515,6 +523,9 @@ class DirSrv(SimpleLDAPObject):
                 raise IOError('invalid file name or rights: %s' % filename)
 
             prop = {}
+            prop[CONF_SERVER_ID] = serverid
+            prop[SER_SERVERID_PROP] = serverid
+            prop[SER_DEPLOYED_DIR] = self.prefix
             myfile = open(filename, 'r')
             for line in myfile:
                 # retrieve the value in line:: <PROPNAME>=<string> [';' export <PROPNAME>]
@@ -545,6 +556,31 @@ class DirSrv(SimpleLDAPObject):
                     if test_and_set(prop, property, variable, value):
                         break
 
+            # Now, we have passed the sysconfig environment file.
+            #  read in and parse the dse.ldif to determine our SER_* values.
+            # probably should use path join?
+            dsefile = '%s/dse.ldif' % prop[CONF_CONFIG_DIR]
+            if os.path.exists(dsefile):
+                ldifconn = LDIFConn(dsefile)
+                configentry = ldifconn.get(DN_CONFIG)
+                for key in args_dse_keys:
+                    prop[key] = configentry.getValue(args_dse_keys[key])
+                #SER_HOST            (host) nsslapd-localhost
+                #SER_PORT            (port) nsslapd-port
+                #SER_SECURE_PORT     (sslport) nsslapd-secureport
+                #SER_ROOT_DN         (binddn) nsslapd-rootdn
+                #SER_ROOT_PW         (bindpw) We can't do this
+                #SER_CREATION_SUFFIX (creation_suffix) nsslapd-defaultnamingcontext
+                #SER_USER_ID         (userid) nsslapd-localuser
+                #SER_SERVERID_PROP   (serverid) Already have this
+                #SER_GROUP_ID        (groupid) ???
+                #SER_DEPLOYED_DIR    (prefix) Already provided to do the discovery
+                #SER_BACKUP_INST_DIR (backupdir) nsslapd-bakdir <<-- maybe?
+                # We need to convert these two to int
+                #  because other routines get confused if we don't
+                for intkey in [SER_PORT, SER_SECURE_PORT]:
+                    if prop[intkey] is not None:
+                        prop[intkey] = int(prop[intkey])
             return prop
 
         def search_dir(instances, pattern, stop_value=None):
@@ -565,6 +601,7 @@ class DirSrv(SimpleLDAPObject):
                                         not find it.
             '''
             added = False
+            print(pattern)
             for instance in glob.glob(pattern):
                 serverid = os.path.basename(instance)[len(DEFAULT_ENV_HEAD):]
 
@@ -575,7 +612,7 @@ class DirSrv(SimpleLDAPObject):
                 # it is found, store its properties in the list
                 if stop_value:
                     if stop_value == serverid:
-                        instances.append(_parse_configfile(instance))
+                        instances.append(_parse_configfile(instance, serverid))
                         added = True
                         break
                     else:
@@ -583,7 +620,7 @@ class DirSrv(SimpleLDAPObject):
                         continue
                 else:
                     # we are not looking for a specific value, just add it
-                    instances.append(_parse_configfile(instance))
+                    instances.append(_parse_configfile(instance, serverid))
 
             return added
 
@@ -600,7 +637,10 @@ class DirSrv(SimpleLDAPObject):
         #    inst: <prefix>/etc/dirsrv/slapd-<serverid>  (conf)
         #
 
-        prefix = self.prefix or '/'
+        # Don't need a default value now since it's set in init.
+        prefix = self.prefix
+        if serverid is None and hasattr(self, 'serverid'):
+            serverid = self.serverid
 
         # first identify the directories we will scan
         confdir = os.getenv('INITCONFIGDIR')
@@ -632,7 +672,7 @@ class DirSrv(SimpleLDAPObject):
             # first check the private repository
             if privconfig_head:
                 pattern = "%s*" % os.path.join(privconfig_head, DEFAULT_ENV_HEAD)
-                found = search_dir(instances, pattern, self.serverid)
+                found = search_dir(instances, pattern, serverid)
                 if len(instances) > 0:
                     self.log.info("List from %s" % privconfig_head)
                     for instance in instances:
@@ -647,7 +687,7 @@ class DirSrv(SimpleLDAPObject):
             # second, if not already found, search the system repository
             if not found:
                 pattern = "%s*" % os.path.join(sysconfig_head, DEFAULT_ENV_HEAD)
-                search_dir(instances, pattern, self.serverid)
+                search_dir(instances, pattern, serverid)
                 if len(instances) > 0:
                     self.log.info("List from %s" % privconfig_head)
                     for instance in instances:
diff --git a/lib389/_constants.py b/lib389/_constants.py
index 2b4c6d5..96b008e 100644
--- a/lib389/_constants.py
+++ b/lib389/_constants.py
@@ -78,6 +78,7 @@ ENV_SYSCONFIG_DIR = '/etc/sysconfig'
 ENV_LOCAL_DIR = '.dirsrv'
 
 # CONFIG file (<prefix>/etc/sysconfig/dirsrv-* or $HOME/.dirsrv/dirsrv-*) keywords
+CONF_SERVER_ID     = 'SERVER_ID'
 CONF_SERVER_DIR    = 'SERVER_DIR'
 CONF_SERVERBIN_DIR = 'SERVERBIN_DIR'
 CONF_CONFIG_DIR    = 'CONFIG_DIR'
@@ -428,3 +429,17 @@ args_instance = {
                    SER_SERVERID_PROP: "template",
                    SER_CREATION_SUFFIX: DEFAULT_SUFFIX}
 
+# Helper for linking dse.ldif values to the parse_config function
+args_dse_keys = {
+                SER_HOST: 'nsslapd-localhost',
+                SER_PORT: 'nsslapd-port',
+                SER_SECURE_PORT: 'nsslapd-secureport',
+                SER_ROOT_DN: 'nsslapd-rootdn',
+                #SER_ROOT_PW         (bindpw) We can't do this
+                SER_CREATION_SUFFIX: 'nsslapd-defaultnamingcontext',
+                SER_USER_ID: 'nsslapd-localuser',
+                #SER_SERVERID_PROP   (serverid) Already have this set in other areas.
+                #SER_GROUP_ID        (groupid) ???
+                #SER_DEPLOYED_DIR    (prefix) Already provided to do the discovery
+                #SER_BACKUP_INST_DIR (backupdir) nsslapd-bakdir <<-- maybe?
+}
-- 
2.4.3

