]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Adding command line options for LDAP
authorFrank Brehm <frank.brehm@pixelpark.com>
Tue, 30 Jan 2024 13:31:17 +0000 (14:31 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Tue, 30 Jan 2024 13:31:17 +0000 (14:31 +0100)
lib/pp_admintools/app/ldap.py
lib/pp_admintools/argparse_actions.py
locale/de_DE/LC_MESSAGES/pp_admintools.po

index 50d8c14d11164dc42461e9ef38fc20fb3d9534b6..e7b5b3fe1960eac7a79c1201733e805f3ea39b66 100644 (file)
@@ -47,6 +47,8 @@ from .. import DEFAULT_CONFIG_DIR
 from .. import __version__ as GLOBAL_VERSION
 from .. import pp
 from ..argparse_actions import LdapPortOptionAction
+from ..argparse_actions import LimitedFloatOptionAction
+from ..argparse_actions import LimitedIntegerOptionAction
 from ..argparse_actions import PasswordFileOptionAction
 from ..config.ldap import DEFAULT_TIMEOUT
 from ..config.ldap import LdapConfiguration, LdapConnectionInfo
@@ -59,7 +61,7 @@ from ..errors import DpxLdapSessionError
 from ..errors import DpxWriteLdapItemError
 from ..xlate import XLATOR, format_list
 
-__version__ = '1.1.0'
+__version__ = '1.2.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -75,8 +77,11 @@ class BaseLdapApplication(BaseDPXApplication):
     show_cmdline_ldap_timeout = True
     apply_default_ldap_instance_if_not_given = True
     default_default_ldap_instance = 'default'
-    default_wait_on_read_error = 1
+    default_retries_on_conn_error = 5
+    default_wait_on_conn_error = 1.0
     default_single_session = False
+    max_retries_on_conn_error = 100
+    max_wait_on_conn_error = 600
 
     # pattern_re_ldap_dn = (
     # '^([a-z][a-z0-9-]*)=(?![ #])(((?![\\="+,;<>]).)|(\\[ \\#="+,;<>])|(\\[a-f0-9][a-f0-9]))*'
@@ -163,14 +168,15 @@ class BaseLdapApplication(BaseDPXApplication):
         self, appname=None, verbose=0, version=GLOBAL_VERSION, base_dir=None,
             cfg_class=LdapConfiguration, initialized=False, usage=None, description=None,
             argparse_epilog=None, argparse_prefix_chars='-', env_prefix=None,
-            config_dir=DEFAULT_CONFIG_DIR, wait_on_read_error=None,
-            single_session=None):
+            config_dir=DEFAULT_CONFIG_DIR, wait_on_conn_error=None,
+            retries_on_conn_error=None, single_session=None):
         """Contrict the application object."""
         self._password_file = None
         self.ldap_instances = []
         self.ldap_server = {}
         self.ldap_connection = {}
-        self._wait_on_read_error = self.default_wait_on_read_error
+        self._wait_on_conn_error = self.default_wait_on_conn_error
+        self._retries_on_conn_error = self.default_retries_on_conn_error
         self._single_session = self.default_single_session
 
         super(BaseLdapApplication, self).__init__(
@@ -180,8 +186,11 @@ class BaseLdapApplication(BaseDPXApplication):
             env_prefix=env_prefix, config_dir=config_dir
         )
 
-        if wait_on_read_error:
-            self.wait_on_read_error = wait_on_read_error
+        if wait_on_conn_error:
+            self.wait_on_conn_error = wait_on_conn_error
+
+        if retries_on_conn_error is not None:
+            self.retries_on_conn_error = retries_on_conn_error
 
         if single_session is not None:
             self.single_session = single_session
@@ -216,18 +225,44 @@ class BaseLdapApplication(BaseDPXApplication):
 
     # -----------------------------------------------------------
     @property
-    def wait_on_read_error(self):
-        """Return the time in seconds to wait after a unseccessful read for the next try."""
-        return self._wait_on_read_error
+    def wait_on_conn_error(self):
+        """Return the time in seconds to wait after a connection error for the next try."""
+        return self._wait_on_conn_error
 
-    @wait_on_read_error.setter
-    def wait_on_read_error(self, value):
+    @wait_on_conn_error.setter
+    def wait_on_conn_error(self, value):
         val = float(value)
-        if self._wait_on_read_error <= 0:
+        if val <= 0:
             msg = _('The value {v} for {n} must be greater than zero.').format(
-                v=val, n='wait_on_read_error')
+                v=val, n='wait_on_conn_error')
+            raise ValueError(msg)
+        if val > self.max_wait_on_conn_error:
+            msg = _('The value {v} for {n} must not be greater than {m}.').format(
+                v=val, n='wait_on_conn_error', m=self.max_wait_on_conn_error)
+            raise ValueError(msg)
+        self._wait_on_conn_error = val
+
+    # -----------------------------------------------------------
+    @property
+    def retries_on_conn_error(self):
+        """Return the time in seconds to wait after a connection error for the next try."""
+        return self._retries_on_conn_error
+
+    @retries_on_conn_error.setter
+    def retries_on_conn_error(self, value):
+        if value is None:
+            self._retries_on_conn_error = self.default_retries_on_conn_error
+            return
+        val = int(value)
+        if val < 0:
+            msg = _('The value {v} for {n} must be equal to or greater than zero.').format(
+                v=val, n='retries_on_conn_error')
             raise ValueError(msg)
-        self._wait_on_read_error = val
+        if val > self.max_retries_on_conn_error:
+            msg = _('The value {v} for {n} must not be greater than {m}.').format(
+                v=val, n='retries_on_conn_error', m=self.max_retries_on_conn_error)
+            raise ValueError(msg)
+        self._retries_on_conn_error = val
 
     # -----------------------------------------------------------
     @property
@@ -261,13 +296,16 @@ class BaseLdapApplication(BaseDPXApplication):
         res = super(BaseLdapApplication, self).as_dict(short=short)
 
         res['default_default_ldap_instance'] = self.default_default_ldap_instance
+        res['max_retries_on_conn_error'] = self.max_retries_on_conn_error
+        res['max_wait_on_conn_error'] = self.max_wait_on_conn_error
         res['pattern_re_ldap_dn'] = self.pattern_re_ldap_dn
         res['password_file'] = self.password_file
+        res['retries_on_conn_error'] = self.retries_on_conn_error
         res['single_session'] = self.single_session
         res['show_cmdline_ldap_timeout'] = self.show_cmdline_ldap_timeout
         res['use_default_ldap_connection'] = self.use_default_ldap_connection
         res['use_multiple_ldap_connections'] = self.use_multiple_ldap_connections
-        res['wait_on_read_error'] = self.wait_on_read_error
+        res['wait_on_conn_error'] = self.wait_on_conn_error
 
         return res
 
@@ -407,6 +445,25 @@ class BaseLdapApplication(BaseDPXApplication):
                         'root for the LDAP searches.')
                 )
 
+        ldap_group.add_argument(
+            '--retries-on-conn-error', metavar=_('COUNT'), dest='retries', type=int,
+            action=LimitedIntegerOptionAction, min_val=0, max_val=self.max_retries_on_conn_error,
+            include_limits=True,
+            help=_(
+                'The number of retries for executing a LDAP request, which was failing because '
+                'of a connection problem. Default: {}').format(self.retries_on_conn_error)
+        )
+
+        ldap_group.add_argument(
+            '--wait-on-conn-error', metavar=_('SECONDS'), dest='wait_on_conn_error', type=float,
+            action=LimitedFloatOptionAction, min_val=0, max_val=self.max_wait_on_conn_error,
+            include_limits=False,
+            help=_(
+                'The time in seconds after a failed LDAP request, which was failing because '
+                'of a connection problem, for the next try. Default: {}').format(
+                    self.wait_on_conn_error)
+        )
+
         if self.show_cmdline_ldap_timeout:
             ldap_group.add_argument(
                 '-T', '--timeout', metavar=_('SECONDS'), dest='ldap_timeout',
@@ -437,6 +494,14 @@ class BaseLdapApplication(BaseDPXApplication):
         if v:
             self.cfg.ldap_timeout = v
 
+        v = getattr(self.args, 'retries', None)
+        if v is not None:
+            self.retries_on_conn_error = v
+
+        v = getattr(self.args, 'wait_on_conn_error', None)
+        if v is not None:
+            self.wait_on_conn_error = v
+
         if self.prompt_timeout > self.cfg.ldap_timeout:
             msg = _('Limiting console timeout to {} seconds due to LDAP timeout.').format(
                 self.cfg.ldap_timeout)
@@ -1310,7 +1375,7 @@ class BaseLdapApplication(BaseDPXApplication):
                 if cur_try >= tries:
                     break
                 LOG.debug(_('Waiting because of a failing read operation.'))
-                time.sleep(self.wait_on_read_error)
+                time.sleep(self.wait_on_conn_error)
 
             except LDAPSessionTerminatedByServerError as e:
                 msg = _('Session terminated on reading entry {dn!r} from instance {i!r}:').format(
index 49f922d0c8030151cf632ad5fd34d7118c58b637..e2c54b1a0c5a9e3554fd818d382ca61f7c6186d2 100644 (file)
@@ -21,7 +21,7 @@ except ImportError:
 from . import MAX_PORT_NUMBER
 from .xlate import XLATOR
 
-__version__ = '0.4.0'
+__version__ = '0.5.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -72,6 +72,14 @@ class NonNegativeIntegerOptionAction(argparse.Action):
     It ensures, that the given value is an integer value, which ist greater or equal to 0.
     """
 
+    # -------------------------------------------------------------------------
+    def __init__(self, option_strings, may_zero=True, *args, **kwargs):
+        """Initialize the NonNegativeIntegerOptionAction object."""
+        self.may_zero = bool(may_zero)
+
+        super(NonNegativeIntegerOptionAction, self).__init__(
+            option_strings=option_strings, *args, **kwargs)
+
     # -------------------------------------------------------------------------
     def __call__(self, parser, namespace, value, option_string=None):
         """Check the given value from command line for type and the valid range."""
@@ -86,6 +94,47 @@ class NonNegativeIntegerOptionAction(argparse.Action):
             msg = _('The option must not be negative (given: {}).').format(value)
             raise argparse.ArgumentError(self, msg)
 
+        if not self.may_zero and val == 0:
+            msg = _('The option must not be zero.')
+            raise argparse.ArgumentError(self, msg)
+
+        setattr(namespace, self.dest, val)
+
+
+# =============================================================================
+class NonNegativeFloatOptionAction(argparse.Action):
+    """
+    It's an argparse action class to ensure a positive float value.
+
+    It ensures, that the given value is an float value, which ist greater or equal to 0.
+    """
+
+    # -------------------------------------------------------------------------
+    def __init__(self, option_strings, may_zero=True, *args, **kwargs):
+        """Initialize the NonNegativeFloatOptionAction object."""
+        self.may_zero = bool(may_zero)
+
+        super(NonNegativeFloatOptionAction, self).__init__(
+            option_strings=option_strings, *args, **kwargs)
+
+    # -------------------------------------------------------------------------
+    def __call__(self, parser, namespace, value, option_string=None):
+        """Check the given value from command line for type and the valid range."""
+        try:
+            val = float(value)
+        except Exception as e:
+            msg = _('Got a {c} for converting {v!r} into a float value: {e}').format(
+                c=e.__class__.__name__, v=value, e=e)
+            raise argparse.ArgumentError(self, msg)
+
+        if val < 0:
+            msg = _('The option must not be negative (given: {}).').format(value)
+            raise argparse.ArgumentError(self, msg)
+
+        if not self.may_zero and val == 0:
+            msg = _('The option must not be zero.')
+            raise argparse.ArgumentError(self, msg)
+
         setattr(namespace, self.dest, val)
 
 
@@ -94,10 +143,13 @@ class LimitedIntegerOptionAction(argparse.Action):
     """It's an argparse action class to ensure an integer value in a defined range."""
 
     # -------------------------------------------------------------------------
-    def __init__(self, option_strings, min_val=None, max_val=None, *args, **kwargs):
+    def __init__(
+            self, option_strings, min_val=None, max_val=None, include_limits=True,
+            *args, **kwargs):
         """Initialize the LimitedIntegerOptionAction object."""
         self._min_val = min_val
         self._max_val = max_val
+        self.include_limits = include_limits
 
         super(LimitedIntegerOptionAction, self).__init__(
             option_strings=option_strings, *args, **kwargs)
@@ -114,16 +166,28 @@ class LimitedIntegerOptionAction(argparse.Action):
             raise argparse.ArgumentError(self, msg)
 
         if self._min_val is not None:
-            if val < self._min_val:
-                msg = _('The option must be greater or equal to {m} (given: {v}).').format(
-                    m=self._min_val, v=val)
-                raise argparse.ArgumentError(self, msg)
+            if self.include_limits:
+                if val < self._min_val:
+                    msg = _('The option must be greater or equal to {m} (given: {v}).').format(
+                        m=self._min_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
+            else:
+                if val <= self._min_val:
+                    msg = _('The option must be greater than {m} (given: {v}).').format(
+                        m=self._min_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
 
         if self._max_val is not None:
-            if val > self._max_val:
-                msg = _('The option must be less or equal to {m} (given: {v}).').format(
-                    m=self._max_val, v=val)
-                raise argparse.ArgumentError(self, msg)
+            if self.include_limits:
+                if val > self._max_val:
+                    msg = _('The option must be less or equal to {m} (given: {v}).').format(
+                        m=self._max_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
+            else:
+                if val >= self._min_val:
+                    msg = _('The option must be less than {m} (given: {v}).').format(
+                        m=self._max_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
 
         setattr(namespace, self.dest, val)
 
@@ -133,10 +197,13 @@ class LimitedFloatOptionAction(argparse.Action):
     """It's an argparse action class to ensure an float value in a defined range."""
 
     # -------------------------------------------------------------------------
-    def __init__(self, option_strings, min_val=None, max_val=None, *args, **kwargs):
+    def __init__(
+            self, option_strings, min_val=None, max_val=None, include_limits=True,
+            *args, **kwargs):
         """Initialize the LimitedFloatOptionAction object."""
         self._min_val = min_val
         self._max_val = max_val
+        self.include_limits = include_limits
 
         super(LimitedFloatOptionAction, self).__init__(
             option_strings=option_strings, *args, **kwargs)
@@ -152,15 +219,29 @@ class LimitedFloatOptionAction(argparse.Action):
                 c=e.__class__.__name__, v=value, e=e)
             raise argparse.ArgumentError(self, msg)
 
-        if self._min_val is not None and val < self._min_val:
-            msg = _('The option must be greater or equal to {m} (given: {v}).').format(
-                m=self._min_val, v=val)
-            raise argparse.ArgumentError(self, msg)
+        if self._min_val is not None:
+            if self.include_limits:
+                if val < self._min_val:
+                    msg = _('The option must be greater or equal to {m} (given: {v}).').format(
+                        m=self._min_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
+            else:
+                if val <= self._min_val:
+                    msg = _('The option must be greater than {m} (given: {v}).').format(
+                        m=self._min_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
 
-        if self._max_val is not None and val > self._max_val:
-            msg = _('The option must be less or equal to {m} (given: {v}).').format(
-                m=self._max_val, v=val)
-            raise argparse.ArgumentError(self, msg)
+        if self._max_val is not None:
+            if self.include_limits:
+                if val > self._max_val:
+                    msg = _('The option must be less or equal to {m} (given: {v}).').format(
+                        m=self._max_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
+            else:
+                if val >= self._max_val:
+                    msg = _('The option must be less than {m} (given: {v}).').format(
+                        m=self._max_val, v=val)
+                    raise argparse.ArgumentError(self, msg)
 
         setattr(namespace, self.dest, val)
 
index 79d8d452dccac9430db6477bfb362c2e0b57f84c..c6c90e88db013a15b45524a48a6470b91161ddc2 100644 (file)
@@ -2403,7 +2403,7 @@ msgstr "Habe ein {c} bei der Konvertierung von {v!r} in einen Integer-Wert erhal
 
 #: lib/pp_admintools/argparse_actions.py:81
 msgid "The option must not be negative (given: {})."
-msgstr "Die Option darf nich negativ sein (gegeben: {})."
+msgstr "Die Option darf nicht negativ sein (gegeben: {})."
 
 #: lib/pp_admintools/argparse_actions.py:113 lib/pp_admintools/argparse_actions.py:151
 msgid "The option must be greater or equal to {m} (given: {v})."