#50614 Issue 50545 - Port repl-monitor.pl to lib389 CLI
Closed 3 years ago by spichugi. Opened 4 years ago by spichugi.
spichugi/389-ds-base repl-monitor-port  into  master

file modified
+2 -1
@@ -16,6 +16,7 @@ 

  import signal

  import json

  import ast

+ from lib389._constants import DSRC_HOME

  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
@@ -109,7 +110,7 @@ 

      log.debug("Inspired by works of: ITS, The University of Adelaide")

  

      # Now that we have our args, see how they relate with our instance.

-     dsrc_inst = dsrc_to_ldap("~/.dsrc", args.instance, log.getChild('dsrc'))

+     dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))

  

      # Now combine this with our arguments

      dsrc_inst = dsrc_arg_concat(args, dsrc_inst)

file modified
+2 -1
@@ -15,6 +15,7 @@ 

  import argcomplete

  import sys

  import signal

+ from lib389._constants import DSRC_HOME

  from lib389.cli_idm import account as cli_account

  from lib389.cli_idm import initialise as cli_init

  from lib389.cli_idm import organizationalunit as cli_ou
@@ -96,7 +97,7 @@ 

      log.debug("Inspired by works of: ITS, The University of Adelaide")

  

      # Now that we have our args, see how they relate with our instance.

-     dsrc_inst = dsrc_to_ldap("~/.dsrc", args.instance, log.getChild('dsrc'))

+     dsrc_inst = dsrc_to_ldap(DSRC_HOME, args.instance, log.getChild('dsrc'))

  

      # Now combine this with our arguments

  

@@ -346,4 +346,5 @@ 

  # Helper for linking dse.ldif values to the parse_config function

  args_dse_keys = SER_PROPNAME_TO_ATTRNAME

  

+ DSRC_HOME = '~/.dsrc'

  DSRC_CONTAINER = '/data/config/container.inf'

@@ -366,7 +366,7 @@ 

              return (json.dumps(result))

          else:

              retstr = (

-                 "Status for agreement: \"%(cn)s\" (%(nsDS5ReplicaHost)s:"

+                 "Status For Agreement: \"%(cn)s\" (%(nsDS5ReplicaHost)s:"

                  "%(nsDS5ReplicaPort)s)" "\n"

                  "Replica Enabled: %(nsds5ReplicaEnabled)s" "\n"

                  "Update In Progress: %(nsds5replicaUpdateInProgress)s" "\n"

@@ -8,8 +8,10 @@ 

  

  import sys

  import os

+ import json

  import ldap

- from lib389.properties import *

+ from lib389.properties import (SER_LDAP_URL, SER_ROOT_DN, SER_LDAPI_ENABLED,

+                                SER_LDAPI_SOCKET, SER_LDAPI_AUTOBIND)

  from lib389._constants import DSRC_CONTAINER

  

  MAJOR, MINOR, _, _, _ = sys.version_info
@@ -17,6 +19,7 @@ 

  if MAJOR >= 3:

      import configparser

  

+ 

  def dsrc_arg_concat(args, dsrc_inst):

      """

      Given a set of argparse args containing:
@@ -65,6 +68,23 @@ 

          dsrc_inst['starttls'] = True

      return dsrc_inst

  

+ 

+ def _read_dsrc(path, log, case_sensetive=False):

+     path = os.path.expanduser(path)

+     log.debug("dsrc path: %s" % path)

+     log.debug("dsrc container path: %s" % DSRC_CONTAINER)

+     config = configparser.ConfigParser()

+     if case_sensetive:

+         config.optionxform = str

+     # First read our container config if it exists

+     # Then overlap the user config.

+     config.read([DSRC_CONTAINER, path])

+ 

+     log.debug("dsrc instances: %s" % config.sections())

+ 

+     return config

+ 

+ 

  def dsrc_to_ldap(path, instance_name, log):

      """

      Given a path to a file, return the required details for an instance.
@@ -84,15 +104,7 @@ 

      tls_reqcert = [never, hard, allow]

      starttls = [true, false]

      """

-     path = os.path.expanduser(path)

-     log.debug("dsrc path: %s" % path)

-     log.debug("dsrc container path: %s" % DSRC_CONTAINER)

-     config = configparser.ConfigParser()

-     # First read our container config if it exists

-     # Then overlap the user config.

-     config.read([DSRC_CONTAINER, path])

- 

-     log.debug("dsrc instances: %s" % config.sections())

+     config = _read_dsrc(path, log)

  

      # Does our section exist?

      if not config.has_section(instance_name):
@@ -109,14 +121,15 @@ 

      dsrc_inst['binddn'] = config.get(instance_name, 'binddn', fallback=None)

      dsrc_inst['saslmech'] = config.get(instance_name, 'saslmech', fallback=None)

      if dsrc_inst['saslmech'] is not None and dsrc_inst['saslmech'] not in ['EXTERNAL', 'PLAIN']:

-         raise Exception("~/.dsrc [%s] saslmech must be one of EXTERNAL or PLAIN" % instance_name)

+         raise Exception("%s [%s] saslmech must be one of EXTERNAL or PLAIN" % (path, instance_name))

  

      dsrc_inst['tls_cacertdir'] = config.get(instance_name, 'tls_cacertdir', fallback=None)

      dsrc_inst['tls_cert'] = config.get(instance_name, 'tls_cert', fallback=None)

      dsrc_inst['tls_key'] = config.get(instance_name, 'tls_key', fallback=None)

      dsrc_inst['tls_reqcert'] = config.get(instance_name, 'tls_reqcert', fallback='hard')

      if dsrc_inst['tls_reqcert'] not in ['never', 'allow', 'hard']:

-         raise Exception("dsrc tls_reqcert value invalid. ~/.dsrc [%s] tls_reqcert should be one of never, allow or hard" % instance_name)

+         raise Exception("dsrc tls_reqcert value invalid. %s [%s] tls_reqcert should be one of never, allow or hard" % (instance_name,

+                                                                                                                        path))

      if dsrc_inst['tls_reqcert'] == 'never':

          dsrc_inst['tls_reqcert'] = ldap.OPT_X_TLS_NEVER

      elif dsrc_inst['tls_reqcert'] == 'allow':
@@ -133,9 +146,45 @@ 

          dsrc_inst['args'][SER_LDAPI_SOCKET] = dsrc_inst['uri'][9:]

          dsrc_inst['args'][SER_LDAPI_AUTOBIND] = "on"

  

- 

      # Return the dict.

      log.debug("dsrc completed with %s" % dsrc_inst)

      return dsrc_inst

  

  

+ def dsrc_to_repl_monitor(path, log):

+     """

+     Given a path to a file, return the required details for an instance.

+ 

+     The connection values for monitoring other not connected topologies. The format:

+     'host:port:binddn:bindpwd'. You can use regex for host and port. You can set bindpwd

+     to * and it will be requested at the runtime.

+ 

+     If a host:port is assigned an alias, then the alias instead of host:port will be

+     displayed in the output. The format: "alias=host:port".

+ 

+     The file should be an ini file, and instance should identify a section.

+ 

+     The ini fileshould have the content:

+ 

+     [repl-monitor-connections]

+     connection1 = server1.example.com:38901:cn=Directory manager:*

+     connection2 = server2.example.com:38902:cn=Directory manager:[~/pwd.txt]

+     connection3 = hub1.example.com:.*:cn=Directory manager:password

+ 

+     [repl-monitor-aliases]

+     M1 = server1.example.com:38901

+     M2 = server2.example.com:38902

+     """

+ 

+     config = _read_dsrc(path, log, case_sensetive=True)

+     dsrc_repl_monitor = {"connections": None,

+                          "aliases": None}

+ 

+     if config.has_section("repl-monitor-connections"):

+         dsrc_repl_monitor["connections"] = [conn for _, conn in config.items("repl-monitor-connections")]

+ 

+     if config.has_section("repl-monitor-aliases"):

+         dsrc_repl_monitor["aliases"] = {alias: inst for alias, inst in config.items("repl-monitor-aliases")}

+ 

+     log.debug(f"dsrc completed with {dsrc_repl_monitor}")

+     return dsrc_repl_monitor

@@ -6,16 +6,16 @@ 

  # See LICENSE for details.

  # --- END COPYRIGHT BLOCK ---

  

+ import re

  import logging

- import time

- import base64

  import os

  import json

  import ldap

  from getpass import getpass

- from lib389._constants import *

- from lib389.utils import is_a_dn, ensure_str

- from lib389.replica import Replicas, BootstrapReplicationManager, RUV, Changelog5, ChangelogLDIF

+ from lib389._constants import ReplicaRole, DSRC_HOME

+ from lib389.cli_base.dsrc import dsrc_to_repl_monitor

+ from lib389.utils import is_a_dn

+ from lib389.replica import Replicas, ReplicationMonitor, BootstrapReplicationManager, Changelog5, ChangelogLDIF

  from lib389.tasks import CleanAllRUVTask, AbortCleanAllRUVTask

  from lib389._mapped_object import DSLdapObjects

  
@@ -338,6 +338,90 @@ 

      log.info("Successfully updated replication configuration")

  

  

+ def get_repl_monitor_info(inst, basedn, log, args):

+     connection_data = dsrc_to_repl_monitor(DSRC_HOME, log)

+ 

+     # Additional details for the connections to the topology

+     def get_credentials(host, port):

+         found = False

+         if args.connections:

+             connections = args.connections

+         elif connection_data["connections"]:

+             connections = connection_data["connections"]

+         else:

+             connections = []

+ 

+         if connections:

+             for connection_str in connections:

+                 if len(connection_str.split(":")) != 4:

+                     raise ValueError(f"Connection string {connection_str} is in wrong format."

+                                      "It should be host:port:binddn:bindpw")

+                 host_regex = connection_str.split(":")[0]

+                 port_regex = connection_str.split(":")[1]

+                 if re.match(host_regex, host) and re.match(port_regex, port):

+                     found = True

+                     binddn = connection_str.split(":")[2]

+                     bindpw = connection_str.split(":")[3]

+                     # Search for the password file or ask the user to write it

+                     if bindpw.startswith("[") and bindpw.endswith("]"):

+                         pwd_file_path = os.path.expanduser(bindpw[1:][:-1])

+                         try:

+                             with open(pwd_file_path) as f:

+                                 bindpw = f.readline().strip()

+                         except FileNotFoundError:

+                             bindpw = getpass(f"File '{pwd_file_path}' was not found. Please, enter "

+                                              f"a password for {binddn} on {host}:{port}: ").rstrip()

+                     if bindpw == "*":

+                         bindpw = getpass(f"Enter a password for {binddn} on {host}:{port}: ").rstrip()

+         if not found:

+             binddn = input(f'\nEnter a bind DN for {host}:{port}: ').rstrip()

+             bindpw = getpass(f"Enter a password for {binddn} on {host}:{port}: ").rstrip()

+ 

+         return {"binddn": binddn,

+                 "bindpw": bindpw}

+ 

+     repl_monitor = ReplicationMonitor(inst)

+     report_dict = repl_monitor.generate_report(get_credentials)

+ 

+     if args.json:

+         log.info(json.dumps({"type": "list", "items": report_dict}))

+     else:

+         for instance, report_data in report_dict.items():

+             found_alias = False

+             if args.aliases:

+                 aliases = {al.split("=")[0]: al.split("=")[1] for al in args.aliases}

+             elif connection_data["aliases"]:

+                 aliases = connection_data["aliases"]

+             else:

+                 aliases = {}

+             if aliases:

+                 for alias_name, alias_host_port in aliases.items():

+                     if alias_host_port.lower() == instance.lower():

+                         supplier_header = f"Supplier: {alias_name} ({instance})"

+                         found_alias = True

+                         break

+             if not found_alias:

+                 supplier_header = f"Supplier: {instance}"

+             log.info(supplier_header)

+             # Draw a line with the same length as the header

+             log.info("-".join(["" for _ in range(0, len(supplier_header)+1)]))

+             if "status" in report_data and report_data["status"] == "Unavailable":

+                 status = report_data["status"]

+                 reason = report_data["reason"]

+                 log.info(f"Status: {status}")

+                 log.info(f"Reason: {reason}\n")

+             else:

+                 for replica in report_data:

+                     replica_root = replica["replica_root"]

+                     replica_id = replica["replica_id"]

+                     maxcsn = replica["maxcsn"]

+                     log.info(f"Replica Root: {replica_root}")

+                     log.info(f"Replica ID: {replica_id}")

+                     log.info(f"Max CSN: {maxcsn}\n")

+                     for agreement_status in replica["agmts_status"]:

+                         log.info(agreement_status)

+ 

+ 

  def create_cl(inst, basedn, log, args):

      cl = Changelog5(inst)

      try:
@@ -1033,6 +1117,17 @@ 

      repl_set_parser.add_argument('--repl-release-timeout', help="A timeout in seconds a replication master should send "

                                                                  "updates before it yields its replication session")

  

+     repl_monitor_parser = repl_subcommands.add_parser('monitor', help='Get the full replication topology report')

+     repl_monitor_parser.set_defaults(func=get_repl_monitor_info)

+     repl_monitor_parser.add_argument('-c', '--connections', nargs="*",

+                                      help="The connection values for monitoring other not connected topologies. "

+                                           "The format: 'host:port:binddn:bindpwd'. You can use regex for host and port. "

+                                           "You can set bindpwd to * and it will be requested at the runtime or "

+                                           "you can include the path to the password file in square brackets - [~/pwd.txt]")

+     repl_monitor_parser.add_argument('-a', '--aliases', nargs="*",

+                                      help="If a host:port is assigned an alias, then the alias instead of "

+                                           "host:port will be displayed in the output. The format: alias=host:port")

+ #

      ############################################

      # Replication Agmts

      ############################################

file modified
+185 -1
@@ -1381,10 +1381,62 @@ 

          """Return the set of agreements related to this suffix replica

          :param: winsync: If True then return winsync replication agreements,

                           otherwise return teh standard replication agreements.

-         :returns: Agreements object

+         :returns: A list Replicas objects

          """

          return Agreements(self._instance, self.dn, winsync=winsync)

  

+     def get_consumer_replicas(self, get_credentials):

+         """Return the set of consumer replicas related to this suffix replica through its agreements

+ 

+         :param get_credentials: A user-defined callback function which returns the binding credentials

+                                 using given host and port data - {"binddn": "cn=Directory Manager",

+                                 "bindpw": "password"}

+         :returns: Replicas object

+         """

+ 

+         agmts = self.get_agreements()

+         result_replicas = []

+         connections = []

+ 

+         try:

+             for agmt in agmts:

+                 host = agmt.get_attr_val_utf8("nsDS5ReplicaHost")

+                 port = agmt.get_attr_val_utf8("nsDS5ReplicaPort")

+                 protocol = agmt.get_attr_val_utf8("nsDS5ReplicaTransportInfo").lower()

+ 

+                 # The function should be defined outside and

+                 # it should have all the logic for figuring out the credentials

+                 credentials = get_credentials(host, port)

+                 if not credentials["binddn"]:

+                     report_data[supplier] = {"status": "Unavailable",

+                                              "reason": "Bind DN was not specified"}

+                     continue

+ 

+                 # Open a connection to the consumer

+                 consumer = DirSrv(verbose=self._instance.verbose)

+                 args_instance[SER_HOST] = host

+                 if protocol == "ssl" or protocol == "ldaps":

+                     args_instance[SER_SECURE_PORT] = int(port)

+                 else:

+                     args_instance[SER_PORT] = int(port)

+                 args_instance[SER_ROOT_DN] = credentials["binddn"]

+                 args_instance[SER_ROOT_PW] = credentials["bindpw"]

+                 args_standalone = args_instance.copy()

+                 consumer.allocate(args_standalone)

+                 try:

+                     consumer.open()

+                 except ldap.LDAPError as e:

+                     self._log.debug(f"Connection to consumer ({host}:{port}) failed, error: {e}")

+                     raise

+                 connections.append(consumer)

+                 result_replicas.append(Replicas(consumer))

+         except:

+             for conn in connections:

+                 conn.close()

+             raise

+ 

+         return result_replicas

+ 

      def get_rid(self):

          """Return the current replicas RID for this suffix

  
@@ -1412,6 +1464,15 @@ 

  

          return RUV(data)

  

+     def get_maxcsn(self):

+         """Return the current replica's maxcsn for this suffix

+ 

+         :returns: str

+         """

+         replica_id = self.get_rid()

+         replica_ruvs = self.get_ruv()

+         return replica_ruvs._rid_maxcsn.get(replica_id, '00000000000000000000')

+ 

      def get_ruv_agmt_maxcsns(self):

          """Return the in memory ruv of this replica suffix.

  
@@ -2232,3 +2293,126 @@ 

          replicas = Replicas(instance)

          replica = replicas.get(self._suffix)

          return replica.get_rid()

+ 

+ 

+ class ReplicationMonitor(object):

+     """The lib389 replication monitor. This is used to check the status

+     of many instances at once.

+     It also allows to monitor independent topologies and get them into

+     the one combined report.

+ 

+     :param instance: A supplier or hub for replication topology monitoring

+     :type instance: list of DirSrv objects

+     :param logger: A logging interface

+     :type logger: python logging

+     """

+ 

+     def __init__(self, instance, logger=None):

+         self._instance = instance

+         if logger is not None:

+             self._log = logger

+         else:

+             self._log = logging.getLogger(__name__)

+ 

+     def _get_replica_status(self, instance, report_data, use_json):

+         """Load all of the status data to report

+         and add new hostname:port pairs for future processing

+         """

+ 

+         replicas_status = []

+         replicas = Replicas(instance)

+         for replica in replicas.list():

+             replica_id = replica.get_rid()

+             replica_root = replica.get_suffix()

+             replica_maxcsn = replica.get_maxcsn()

+             agmts_status = []

+             agmts = replica.get_agreements()

+             for agmt in agmts.list():

+                 host = agmt.get_attr_val_utf8_l("nsds5replicahost")

+                 port = agmt.get_attr_val_utf8_l("nsds5replicaport")

+                 protocol = agmt.get_attr_val_utf8_l('nsds5replicatransportinfo')

+                 # Supply protocol here because we need it only for connection

+                 # and agreement status is already preformatted for the user output

+                 consumer = f"{host}:{port}:{protocol}"

+                 if consumer not in report_data:

+                     report_data[consumer] = None

+                 agmts_status.append(agmt.status(use_json))

+             replicas_status.append({"replica_id": replica_id,

+                                     "replica_root": replica_root,

+                                     "maxcsn": replica_maxcsn,

+                                     "agmts_status": agmts_status})

+         return replicas_status

+ 

+     def generate_report(self, get_credentials, use_json=False):

+         """Generate a replication report for each supplier or hub and the instances

+         that are connected with it by agreements.

+ 

+         :param get_credentials: A user-defined callback function with parameters (host, port) which returns

+                                 a dictionary with binddn and bindpw keys -

+                                 example values "cn=Directory Manager" and "password"

+         :type get_credentials: function

+         :returns: dict

+         """

+         report_data = {}

+ 

+         initial_inst_key = f"{self._instance.host.lower()}:{str(self._instance.port).lower()}"

+         # Do this on an initial instance to get the agreements to other instances

+         report_data[initial_inst_key] = self._get_replica_status(self._instance, report_data, use_json)

+ 

+         # Check if at least some replica report on other instances was generated

+         repl_exists = False

+ 

+         # While we have unprocessed instances - continue

+         while True:

+             try:

+                 supplier = [host_port for host_port, processed_data in report_data.items() if processed_data is None][0]

+             except IndexError:

+                 break

+ 

+             s_splitted = supplier.split(":")

+             supplier_hostname = s_splitted[0]

+             supplier_port = s_splitted[1]

+             supplier_protocol = s_splitted[2]

+ 

+             # The function should be defined outside and

+             # it should have all the logic for figuring out the credentials.

+             # It is done for flexibility purpuses between CLI, WebUI and lib389 API applications

+             credentials = get_credentials(supplier_hostname, supplier_port)

+             if not credentials["binddn"]:

+                 report_data[supplier] = {"status": "Unavailable",

+                                          "reason": "Bind DN was not specified"}

+                 continue

+ 

+             # Open a connection to the consumer

+             supplier_inst = DirSrv(verbose=self._instance.verbose)

+             args_instance[SER_HOST] = supplier_hostname

+             if supplier_protocol == "ssl" or supplier_protocol == "ldaps":

+                 args_instance[SER_SECURE_PORT] = int(supplier_port)

+             else:

+                 args_instance[SER_PORT] = int(supplier_port)

+             args_instance[SER_ROOT_DN] = credentials["binddn"]

+             args_instance[SER_ROOT_PW] = credentials["bindpw"]

+             args_standalone = args_instance.copy()

+             supplier_inst.allocate(args_standalone)

+             try:

+                 supplier_inst.open()

+             except ldap.LDAPError as e:

+                 self._log.debug(f"Connection to consumer ({supplier_hostname}:{supplier_port}) failed, error: {e}")

+                 report_data[supplier] = {"status": "Unavailable",

+                                          "reason": e.args[0]['desc']}

+                 continue

+ 

+             report_data[supplier] = self._get_replica_status(supplier_inst, report_data, use_json)

+             repl_exists = True

+ 

+         # Now remove the protocol from the name

+         report_data_final = {}

+         for key, value in report_data.items():

+             # We take the initial instance only if it is the only existing part of the report

+             if key != initial_inst_key or not repl_exists:

+                 if not value:

+                     value = {"status": "Unavailable",

+                              "reason": "No replicas were found"}

+                 report_data_final[":".join(key.split(":")[:2])] = value

+ 

+         return report_data_final

Description: Add a new command to 'dsconf replication' CLI.
'dsconf replication monitor' generates a report which
shows the replication topology to which the instance does belong.

Additional arguments:
-c or --connection [CONNECTION [CONNECTION ...]]
The connection values for monitoring other not
connected topologies. The format:
'host:port:binddn:bindpwd'. You can use regex for host
and port.You can set bindpwd to * and it will be
requested at the runtime.
-a or --alias [ALIAS [ALIAS ...]]
If a host:port is assigned an alias, then the alias
instead of host:port will be displayed in the output.
The format: alias=host:port

https://pagure.io/389-ds-base/issue/50545

Reviewed by: ?

Only one minor thing I want to add - .dsrc processing.
But feel free to review the actual tool now. It is working and it has all the logic.

Also, I've added a function get_consumer_replicas but I haven't used it in the code because it would make the logic more complex and harder to understand.
But it can be used by someone in the future, so I've decided to leave it there.

We should also have an option to read in a connection's bind password from a file (that would make things easier in the UI).

Supplier: localhost:5555
------------------------
Replica Root: dc=example,dc=com
Replica ID: 33
Max CSN: 5d88ece0000000210000
-
Status for agreement: "to master" (localhost:389)

What is the line with the "-"? Can it be removed?

dsconf localhost replication monitor --connection localhost:389:cn=dm:password --connection localhost:5555:cn=dm:password 

Enter a bind DN for localhost:389: 

Why am I being prompted for a password since I've already provided one?

Ok, so this is where that "-" was coming from. At first I thought it was a "value" without an "attribute". If you are looking for a separator, then perhaps it should many dashes"------------------------------", otherwise it's confusing. Or, even something with a title like "---- Agreement Details ----"

dsconf localhost replication monitor --connection localhost:389:cn=dm:password --connection localhost:5555:cn=dm:password

Enter a bind DN for localhost:389:

Why am I being prompted for a password since I've already provided one?

Okay I see I misused the CLI. It expects only one "--connection" parameter. I think we should change this so it's a one-to-one relationship. One arg per connection:

# dsconf localhost replication monitor -c <connection> -c <connection> -c <connection>

Also :-) The old script accepted a config file which was just a list of connections. I think dsconf should accept a config/connection file as well, and it should accept the same format used in the old tool so customers can just reuse the same file.

rebased onto 61180519a9e02739c84996e58b5dd8a27ebb4635

4 years ago

We should also have an option to read in a connection's bind password from a file (that would make things easier in the UI).

Added.

What is the line with the "-"? Can it be removed?

Sure. I just copied the thing from the original report but I agree it looks cleaner without it. Removing.

dsconf localhost replication monitor --connection localhost:389:cn=dm:password --connection localhost:5555:cn=dm:password
Enter a bind DN for localhost:389:
Why am I being prompted for a password since I've already provided one?

Okay I see I misused the CLI. It expects only one "--connection" parameter. I think we should change this so it's a one-to-one relationship. One arg per connection:
dsconf localhost replication monitor -c <connection> -c <connection> -c <connection>

You can specify multiple args like this:

# dsconf localhost replication monitor -c <connection> <connection> <connection>

I think the option you propose will make the CLI more confusing... argparse originally uses nargs="*" which gives you - -c [CONNECTION [CONNECTION ...]] help usage. Which is consistent and more compact.

Also :-) The old script accepted a config file which was just a list of connections. I think dsconf should accept a config/connection file as well, and it should accept the same format used in the old tool so customers can just reuse the same file.

Yep, just added. Though I've changed the format a bit because I use existing ~/.dsrc functionality that I got working. And it uses ConfigParser which has its limitations... (for example, there is no elegant way to specify multiple arguments - what I've chosen is lesser evil)

Example:

[repl-monitor-connection]
connection1 = server1.example.com:38901:cn=Directory manager:*
connection2 = server2.example.com:38901:cn=Directory manager:[~/pwd.txt]
connection3 = hub1.example.com:.*:cn=Directory manager:password

[repl-monitor-alias]
M1 = server1.example.com:38901
M2 = server1.example.com:38902
H1 = hub1.example.com:38902

Please, review.

rebased onto 416c5c7689025736121f4ad2bd5011b3343941f6

4 years ago

You can specify multiple args like this:
dsconf localhost replication monitor -c <connection> <connection> <connection>

I think the option you propose will make the CLI more confusing... argparse originally uses nargs="*" which gives you - -c [CONNECTION [CONNECTION ...]] help usage. Which is consistent and more compact.

Then change the long arg to "--connections" so it's more obvious it takes multiple values.

Yep, just added. Though I've changed the format a bit because I use existing ~/.dsrc functionality that I got working. And it uses ConfigParser which has its limitations... (for example, there is no elegant way to specify multiple arguments - what I've chosen is lesser evil)
Example:
[repl-monitor-connection]
connection1 = server1.example.com:38901:cn=Directory manager:
connection2 = server2.example.com:38901:cn=Directory manager:[~/pwd.txt]
connection3 = hub1.example.com:.
:cn=Directory manager:password

Does the connection name matter?

Then change the long arg to "--connections" so it's more obvious it takes multiple values.

Sure, makes sense. It was another thing I've copied from the original but we better change it, yeah.

Does the connection name matter?

Nope

1 new commit added

  • Replace connection and alias with its plurals
4 years ago

Fixed. Please, review.

Also, the How-To docs are on review too:
https://github.com/marcus2376/389wiki/pull/16

rebased onto edf23ac5e6001cad52dfd9f81416123ca79b1f92

4 years ago

rebased onto 761dd65

4 years ago

Pull-Request has been merged by spichugi

4 years ago

389-ds-base is moving from Pagure to Github. This means that new issues and pull requests
will be accepted only in 389-ds-base's github repository.

This pull request has been cloned to Github as issue and is available here:
- https://github.com/389ds/389-ds-base/issues/3669

If you want to continue to work on the PR, please navigate to the github issue,
download the patch from the attachments and file a new pull request.

Thank you for understanding. We apologize for all inconvenience.

Pull-Request has been closed by spichugi

3 years ago