From 5fe4e21ab4e6823dae8b34ed7e84413b70981504 Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Jun 14 2018 16:06:38 +0000 Subject: Ticket 49777 - add config subcommand to dsconf Description: Added a "config" subcomand to dsconf for getting and setting cn=config attributes. Also fixed JSON issues. https://pagure.io/389-ds-base/issue/49777 Reviewed by: spichugi(Thanks!) --- diff --git a/src/lib389/cli/dsconf b/src/lib389/cli/dsconf index 3292219..d9110c4 100755 --- a/src/lib389/cli/dsconf +++ b/src/lib389/cli/dsconf @@ -18,6 +18,7 @@ logging.basicConfig(format='%(message)s') from lib389 import DirSrv from lib389._constants import DN_CONFIG, DN_DM +from lib389.cli_conf import config as cli_config from lib389.cli_conf import backend as cli_backend from lib389.cli_conf import directory_manager as cli_directory_manager from lib389.cli_conf import plugin as cli_plugin @@ -71,6 +72,7 @@ if __name__ == '__main__': subparsers = parser.add_subparsers(help="resources to act upon") + cli_config.create_parser(subparsers) cli_backend.create_parser(subparsers) cli_directory_manager.create_parsers(subparsers) cli_schema.create_parser(subparsers) @@ -122,7 +124,7 @@ if __name__ == '__main__': if args and args.json: print(e) else: - log.error("Error!: %s" % str(e)) + log.error("Error: %s" % str(e)) result = False disconnect_instance(inst) diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index 9c58efb..70228ed 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -163,12 +163,11 @@ class DSLdapObject(DSLogging): def _jsonify(self, fn, *args, **kwargs): # This needs to map all the values to ensure_str - attrs = fn(*args, **kwargs) + attrs = fn(use_json=True, *args, **kwargs) str_attrs = {} for k in attrs: str_attrs[ensure_str(k)] = ensure_list_str(attrs[k]) - - response = json.dumps({ "type": "entry", "dn": ensure_str(self._dn), "attrs" : str_attrs }); + response = json.dumps({"type": "entry", "dn": ensure_str(self._dn), "attrs": str_attrs}) return response @@ -183,7 +182,7 @@ class DSLdapObject(DSLogging): if (name.endswith('_json')): int_name = name.replace('_json', '') - pfunc = partial(self._jsonify, fn=getattr(self, int_name)) + pfunc = partial(self._jsonify, getattr(self, int_name)) return pfunc # We make this a property so that we can over-ride dynamically if needed @@ -326,10 +325,20 @@ class DSLdapObject(DSLogging): :raises: ValueError - if instance is not online """ + if action == ldap.MOD_ADD: + action_txt = "ADD" + elif action == ldap.MOD_REPLACE: + action_txt = "REPLACE" + elif action == ldap.MOD_DELETE: + action_txt = "DELETE" + else: + # This should never happen (bug!) + action_txt = "UNKNOWN" + if value is None or len(value) < 512: - self._log.debug("%s set(%r, %r)" % (self._dn, key, value)) + self._log.debug("%s set %s: (%r, %r)" % (self._dn, action_txt, key, value)) else: - self._log.debug("%s set(%r, value too large)" % (self._dn, key)) + self._log.debug("%s set %s: (%r, value too large)" % (self._dn, action_txt, key)) if self._instance.state != DIRSRV_STATE_ONLINE: raise ValueError("Invalid state. Cannot set properties on instance that is not ONLINE.") @@ -418,7 +427,7 @@ class DSLdapObject(DSLogging): return False return True - def get_compare_attrs(self): + def get_compare_attrs(self, use_json=False): """Get a dictionary having attributes to be compared i.e. excluding self._compare_exclude """ @@ -430,7 +439,7 @@ class DSLdapObject(DSLogging): compare_attrs_dict = {attr:all_attrs_dict[attr] for attr in compare_attrs} return compare_attrs_dict - def get_all_attrs(self): + def get_all_attrs(self, use_json=False): """Get a dictionary having all the attributes of the entry :returns: Dict with real attributes and operational attributes @@ -446,7 +455,7 @@ class DSLdapObject(DSLogging): attrs_dict = attrs_entry.data return attrs_dict - def get_attrs_vals(self, keys): + def get_attrs_vals(self, keys, use_json=False): self._log.debug("%s get_attrs_vals(%r)" % (self._dn, keys)) if self._instance.state != DIRSRV_STATE_ONLINE: raise ValueError("Invalid state. Cannot get properties on instance that is not ONLINE") @@ -454,7 +463,7 @@ class DSLdapObject(DSLogging): entry = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, self._object_filter, attrlist=keys, serverctrls=self._server_controls, clientctrls=self._client_controls)[0] return entry.getValuesSet(keys) - def get_attr_vals(self, key): + def get_attr_vals(self, key, use_json=False): self._log.debug("%s get_attr_vals(%r)" % (self._dn, key)) # We might need to add a state check for NONE dn. if self._instance.state != DIRSRV_STATE_ONLINE: @@ -465,9 +474,16 @@ class DSLdapObject(DSLogging): # It would be good to prevent the entry code intercepting this .... # We have to do this in this method, because else we ignore the scope base. entry = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, self._object_filter, attrlist=[key], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] - return entry.getValues(key) + vals = entry.getValues(key) + if use_json: + result = {key: []} + for val in vals: + result[key].append(val) + return result + else: + return vals - def get_attr_val(self, key): + def get_attr_val(self, key, use_json=False): self._log.debug("%s getVal(%r)" % (self._dn, key)) # We might need to add a state check for NONE dn. if self._instance.state != DIRSRV_STATE_ONLINE: @@ -478,7 +494,7 @@ class DSLdapObject(DSLogging): entry = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, self._object_filter, attrlist=[key], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] return entry.getValue(key) - def get_attr_val_bytes(self, key): + def get_attr_val_bytes(self, key, use_json=False): """Get a single attribute value from the entry in bytes type :param key: An attribute name @@ -489,7 +505,7 @@ class DSLdapObject(DSLogging): return ensure_bytes(self.get_attr_val(key)) - def get_attr_vals_bytes(self, key): + def get_attr_vals_bytes(self, key, use_json=False): """Get attribute values from the entry in bytes type :param key: An attribute name @@ -500,7 +516,7 @@ class DSLdapObject(DSLogging): return ensure_list_bytes(self.get_attr_vals(key)) - def get_attr_val_utf8(self, key): + def get_attr_val_utf8(self, key, use_json=False): """Get a single attribute value from the entry in utf8 type :param key: An attribute name @@ -511,7 +527,7 @@ class DSLdapObject(DSLogging): return ensure_str(self.get_attr_val(key)) - def get_attr_val_utf8_l(self, key): + def get_attr_val_utf8_l(self, key, use_json=False): """Get a single attribute value from the entry in utf8 type :param key: An attribute name @@ -526,7 +542,7 @@ class DSLdapObject(DSLogging): else: return None - def get_attr_vals_utf8(self, key): + def get_attr_vals_utf8(self, key, use_json=False): """Get attribute values from the entry in utf8 type :param key: An attribute name @@ -537,7 +553,7 @@ class DSLdapObject(DSLogging): return ensure_list_str(self.get_attr_vals(key)) - def get_attr_vals_utf8_l(self, key): + def get_attr_vals_utf8_l(self, key, use_json=False): """Get attribute values from the entry in utf8 type and lowercase :param key: An attribute name @@ -548,7 +564,7 @@ class DSLdapObject(DSLogging): return [x.lower() for x in ensure_list_str(self.get_attr_vals(key))] - def get_attr_val_int(self, key): + def get_attr_val_int(self, key, use_json=False): """Get a single attribute value from the entry in int type :param key: An attribute name @@ -559,7 +575,7 @@ class DSLdapObject(DSLogging): return ensure_int(self.get_attr_val(key)) - def get_attr_vals_int(self, key): + def get_attr_vals_int(self, key, use_json=False): """Get attribute values from the entry in int type :param key: An attribute name @@ -796,7 +812,8 @@ class DSLdapObject(DSLogging): results.append(result) return results -# A challenge of this, is how do we manage indexes? They have two naming attribunes.... + +# A challenge of this, is how do we manage indexes? They have two naming attributes.... class DSLdapObjects(DSLogging): """The object represents the next idea: "Everything is an instance of something diff --git a/src/lib389/lib389/cli_base/__init__.py b/src/lib389/lib389/cli_base/__init__.py index 397778f..0b6ee0c 100644 --- a/src/lib389/lib389/cli_base/__init__.py +++ b/src/lib389/lib389/cli_base/__init__.py @@ -9,6 +9,7 @@ import logging import sys import json +import ldap from getpass import getpass from lib389 import DirSrv @@ -140,14 +141,31 @@ def _generic_list(inst, basedn, log, manager_class, args=None): def _generic_get(inst, basedn, log, manager_class, selector, args=None): mc = manager_class(inst, basedn) if args and args.json: - o = mc.get(selector, json=True) - print(o) + o = mc.get(selector, json=True) + log.info(o) else: o = mc.get(selector) o_str = o.display() log.info(o_str) +def _generic_get_entry(inst, basedn, log, manager_class, args=None): + mc = manager_class(inst, basedn) + if args and args.json: + log.info(mc.get_all_attrs_json()) + else: + log.info(mc.display()) + + +def _generic_get_attr(inst, basedn, log, manager_class, args=None): + mc = manager_class(inst, basedn) + for attr in args.attrs: + if args and args.json: + log.info(mc.get_attr_vals_json(attr)) + else: + log.info(mc.display_attr(attr).rstrip()) + + def _generic_get_dn(inst, basedn, log, manager_class, dn, args=None): mc = manager_class(inst, basedn) o = mc.get(dn=dn) @@ -169,6 +187,56 @@ def _generic_delete(inst, basedn, log, object_class, dn, args=None): log.info('Successfully deleted %s' % dn) +# Attr functions expect attribute values to be "attr=value" + +def _generic_replace_attr(inst, basedn, log, manager_class, args=None): + mc = manager_class(inst, basedn) + if args and args.attr: + for myattr in args.attr: + if "=" in myattr: + [attr, val] = myattr.split("=", 1) + mc.replace(attr, val) + log.info("Successfully replaced \"{}\"".format(attr)) + else: + raise ValueError("You must specify a value to replace the attribute ({})".format(myattr)) + else: + # Missing value + raise ValueError("Missing attribute to replace") + + +def _generic_add_attr(inst, basedn, log, manager_class, args=None): + mc = manager_class(inst, basedn) + if args and args.attr: + for myattr in args.attr: + if "=" in myattr: + [attr, val] = myattr.split("=", 1) + mc.add(attr, val) + log.info("Successfully added \"{}\"".format(attr)) + else: + raise ValueError("You must specify a value to add for the attribute ({})".format(myattr)) + else: + # Missing value + raise ValueError("Missing attribute to add") + + +def _generic_del_attr(inst, basedn, log, manager_class, args=None): + mc = manager_class(inst, basedn) + if args and args.attr: + for myattr in args.attr: + if "=" in myattr: + # we have a specific value + [attr, val] = myattr.split("=", 1) + mc.remove(attr, val) + else: + # remove all + mc.remove_all(myattr) + attr = myattr # for logging + log.info("Successfully removed \"{}\"".format(attr)) + else: + # Missing value + raise ValueError("Missing attribute to delete") + + class LogCapture(logging.Handler): """ This useful class is for intercepting logs, and then making assertions about diff --git a/src/lib389/lib389/cli_conf/config.py b/src/lib389/lib389/cli_conf/config.py new file mode 100644 index 0000000..585a271 --- /dev/null +++ b/src/lib389/lib389/cli_conf/config.py @@ -0,0 +1,60 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2018 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +from lib389.config import Config +from lib389.cli_base import ( + populate_attr_arguments, + _generic_get_entry, + _generic_get_attr, + _generic_add_attr, + _generic_replace_attr, + _generic_del_attr, + ) + + +def config_get(inst, basedn, log, args): + if args and args.attrs: + _generic_get_attr(inst, basedn, log.getChild('config_get'), Config, args) + else: + # Get the entire cn=config entry + _generic_get_entry(inst, basedn, log.getChild('config_get'), Config, args) + return + + +def config_add_attr(inst, basedn, log, args): + _generic_add_attr(inst, basedn, log.getChild('config_add_attr'), Config, args) + + +def config_replace_attr(inst, basedn, log, args): + _generic_replace_attr(inst, basedn, log.getChild('config_replace_attr'), Config, args) + + +def config_del_attr(inst, basedn, log, args): + _generic_del_attr(inst, basedn, log.getChild('config_del_attr'), Config, args) + + +def create_parser(subparsers): + config_parser = subparsers.add_parser('config', help="Manage server configuration") + + subcommands = config_parser.add_subparsers(help="action") + + get_parser = subcommands.add_parser('get', help='get') + get_parser.set_defaults(func=config_get) + get_parser.add_argument('attrs', nargs='*', help='Configuration attribute(s) to get') + + add_attr_parser = subcommands.add_parser('add', help='Add attribute value to configuration') + add_attr_parser.set_defaults(func=config_add_attr) + add_attr_parser.add_argument('attr', nargs='*', help='Configuration attribute to add') + + replace_attr_parser = subcommands.add_parser('replace', help='Replace attribute value in configuration') + replace_attr_parser.set_defaults(func=config_replace_attr) + replace_attr_parser.add_argument('attr', nargs='*', help='Configuration attribute to replace') + + del_attr_parser = subcommands.add_parser('delete', help='Delete attribute value in configuration') + del_attr_parser.set_defaults(func=config_del_attr) + del_attr_parser.add_argument('attr', nargs='*', help='Configuration attribute to delete') diff --git a/src/lib389/lib389/config.py b/src/lib389/lib389/config.py index 5dacd86..cec87e8 100644 --- a/src/lib389/lib389/config.py +++ b/src/lib389/lib389/config.py @@ -31,24 +31,24 @@ class Config(DSLdapObject): - set access and error logging - get and set "cn=config" attributes """ - def __init__(self, conn): + def __init__(self, conn, dn=None): """@param conn - a DirSrv instance """ super(Config, self).__init__(instance=conn) self._dn = DN_CONFIG # self._instance = conn # self.log = conn.log config_compare_exclude = [ - 'nsslapd-ldapifilepath', - 'nsslapd-accesslog', - 'nsslapd-auditfaillog', + 'nsslapd-ldapifilepath', + 'nsslapd-accesslog', + 'nsslapd-auditfaillog', 'nsslapd-ldifdir', - 'nsslapd-errorlog', - 'nsslapd-instancedir', - 'nsslapd-lockdir', + 'nsslapd-errorlog', + 'nsslapd-instancedir', + 'nsslapd-lockdir', 'nsslapd-bakdir', - 'nsslapd-schemadir', + 'nsslapd-schemadir', 'nsslapd-auditlog', - 'nsslapd-rootpw', + 'nsslapd-rootpw', 'nsslapd-workingdir', 'nsslapd-certdir' ]