Blame SOURCES/0008-Issue-3657-Add-options-to-dsctl-for-dsrc-file.patch

3bc51a
From 4faec52810e12070ef72da347bb590c57d8761e4 Mon Sep 17 00:00:00 2001
3bc51a
From: Mark Reynolds <mreynolds@redhat.com>
3bc51a
Date: Fri, 20 Nov 2020 17:47:18 -0500
3bc51a
Subject: [PATCH 1/2] Issue 3657 - Add options to dsctl for dsrc file
3bc51a
3bc51a
Description:  Add options to create, modify, delete, and display
3bc51a
              the .dsrc CLI tool shortcut file.
3bc51a
3bc51a
Relates: https://github.com/389ds/389-ds-base/issues/3657
3bc51a
3bc51a
Reviewed by: firstyear(Thanks!)
3bc51a
---
3bc51a
 dirsrvtests/tests/suites/clu/dsrc_test.py | 136 ++++++++++
3bc51a
 src/lib389/cli/dsctl                      |   2 +
3bc51a
 src/lib389/lib389/cli_ctl/dsrc.py         | 312 ++++++++++++++++++++++
3bc51a
 3 files changed, 450 insertions(+)
3bc51a
 create mode 100644 dirsrvtests/tests/suites/clu/dsrc_test.py
3bc51a
 create mode 100644 src/lib389/lib389/cli_ctl/dsrc.py
3bc51a
3bc51a
diff --git a/dirsrvtests/tests/suites/clu/dsrc_test.py b/dirsrvtests/tests/suites/clu/dsrc_test.py
3bc51a
new file mode 100644
3bc51a
index 000000000..1b27700ec
3bc51a
--- /dev/null
3bc51a
+++ b/dirsrvtests/tests/suites/clu/dsrc_test.py
3bc51a
@@ -0,0 +1,136 @@
3bc51a
+import logging
3bc51a
+import pytest
3bc51a
+import os
3bc51a
+from os.path import expanduser
3bc51a
+from lib389.cli_base import FakeArgs
3bc51a
+from lib389.cli_ctl.dsrc import create_dsrc, modify_dsrc, delete_dsrc, display_dsrc
3bc51a
+from lib389._constants import DEFAULT_SUFFIX, DN_DM
3bc51a
+from lib389.topologies import topology_st as topo
3bc51a
+
3bc51a
+log = logging.getLogger(__name__)
3bc51a
+
3bc51a
+
3bc51a
+@pytest.fixture(scope="function")
3bc51a
+def setup(topo, request):
3bc51a
+    """Preserve any existing .dsrc file"""
3bc51a
+
3bc51a
+    dsrc_file = f'{expanduser("~")}/.dsrc'
3bc51a
+    backup_file = dsrc_file + ".original"
3bc51a
+    if os.path.exists(dsrc_file):
3bc51a
+        os.rename(dsrc_file, backup_file)
3bc51a
+
3bc51a
+    def fin():
3bc51a
+        if os.path.exists(backup_file):
3bc51a
+            os.rename(backup_file, dsrc_file)
3bc51a
+
3bc51a
+    request.addfinalizer(fin)
3bc51a
+
3bc51a
+
3bc51a
+def test_dsrc(topo, setup):
3bc51a
+    """Test "dsctl dsrc" command
3bc51a
+
3bc51a
+    :id: 0610de6c-e167-4761-bdab-3e677b2d44bb
3bc51a
+    :setup: Standalone Instance
3bc51a
+    :steps:
3bc51a
+        1. Test creation works
3bc51a
+        2. Test creating duplicate section
3bc51a
+        3. Test adding an additional inst config works
3bc51a
+        4. Test removing an instance works
3bc51a
+        5. Test modify works
3bc51a
+        6. Test delete works
3bc51a
+        7. Test display fails when no file is present
3bc51a
+
3bc51a
+    :expectedresults:
3bc51a
+        1. Success
3bc51a
+        2. Success
3bc51a
+        3. Success
3bc51a
+        4. Success
3bc51a
+        5. Success
3bc51a
+        6. Success
3bc51a
+        7. Success
3bc51a
+    """
3bc51a
+
3bc51a
+    inst = topo.standalone
3bc51a
+    serverid = inst.serverid
3bc51a
+    second_inst_name = "Second"
3bc51a
+    second_inst_basedn = "o=second"
3bc51a
+    different_suffix = "o=different"
3bc51a
+
3bc51a
+    # Setup our args
3bc51a
+    args = FakeArgs()
3bc51a
+    args.basedn = DEFAULT_SUFFIX
3bc51a
+    args.binddn = DN_DM
3bc51a
+    args.json = None
3bc51a
+    args.uri = None
3bc51a
+    args.saslmech = None
3bc51a
+    args.tls_cacertdir = None
3bc51a
+    args.tls_cert = None
3bc51a
+    args.tls_key = None
3bc51a
+    args.tls_reqcert = None
3bc51a
+    args.starttls = None
3bc51a
+    args.cancel_starttls = None
3bc51a
+    args.pwdfile = None
3bc51a
+    args.do_it = True
3bc51a
+
3bc51a
+    # Create a dsrc configuration entry
3bc51a
+    create_dsrc(inst, log, args)
3bc51a
+    display_dsrc(inst, topo.logcap.log, args)
3bc51a
+    assert topo.logcap.contains("basedn = " + args.basedn)
3bc51a
+    assert topo.logcap.contains("binddn = " + args.binddn)
3bc51a
+    assert topo.logcap.contains("[" + serverid + "]")
3bc51a
+    topo.logcap.flush()
3bc51a
+
3bc51a
+    # Attempt to add duplicate instance section
3bc51a
+    with pytest.raises(ValueError):
3bc51a
+        create_dsrc(inst, log, args)
3bc51a
+
3bc51a
+    # Test adding a second instance works correctly
3bc51a
+    inst.serverid = second_inst_name
3bc51a
+    args.basedn = second_inst_basedn
3bc51a
+    create_dsrc(inst, log, args)
3bc51a
+    display_dsrc(inst, topo.logcap.log, args)
3bc51a
+    assert topo.logcap.contains("basedn = " + args.basedn)
3bc51a
+    assert topo.logcap.contains("[" + second_inst_name + "]")
3bc51a
+    topo.logcap.flush()
3bc51a
+
3bc51a
+    # Delete second instance
3bc51a
+    delete_dsrc(inst, log, args)
3bc51a
+    inst.serverid = serverid  # Restore original instance name
3bc51a
+    display_dsrc(inst, topo.logcap.log, args)
3bc51a
+    assert not topo.logcap.contains("[" + second_inst_name + "]")
3bc51a
+    assert not topo.logcap.contains("basedn = " + args.basedn)
3bc51a
+    # Make sure first instance config is still present
3bc51a
+    assert topo.logcap.contains("[" + serverid + "]")
3bc51a
+    assert topo.logcap.contains("binddn = " + args.binddn)
3bc51a
+    topo.logcap.flush()
3bc51a
+
3bc51a
+    # Modify the config
3bc51a
+    args.basedn = different_suffix
3bc51a
+    modify_dsrc(inst, log, args)
3bc51a
+    display_dsrc(inst, topo.logcap.log, args)
3bc51a
+    assert topo.logcap.contains(different_suffix)
3bc51a
+    topo.logcap.flush()
3bc51a
+
3bc51a
+    # Remove an arg from the config
3bc51a
+    args.basedn = ""
3bc51a
+    modify_dsrc(inst, log, args)
3bc51a
+    display_dsrc(inst, topo.logcap.log, args)
3bc51a
+    assert not topo.logcap.contains(different_suffix)
3bc51a
+    topo.logcap.flush()
3bc51a
+
3bc51a
+    # Remove the last entry, which should delete the file
3bc51a
+    delete_dsrc(inst, log, args)
3bc51a
+    dsrc_file = f'{expanduser("~")}/.dsrc'
3bc51a
+    assert not os.path.exists(dsrc_file)
3bc51a
+
3bc51a
+    # Make sure display fails
3bc51a
+    with pytest.raises(ValueError):
3bc51a
+        display_dsrc(inst, log, args)
3bc51a
+
3bc51a
+
3bc51a
+if __name__ == '__main__':
3bc51a
+    # Run isolated
3bc51a
+    # -s for DEBUG mode
3bc51a
+    CURRENT_FILE = os.path.realpath(__file__)
3bc51a
+    pytest.main(["-s", CURRENT_FILE])
3bc51a
+
3bc51a
diff --git a/src/lib389/cli/dsctl b/src/lib389/cli/dsctl
3bc51a
index fe9bc10e9..69f069297 100755
3bc51a
--- a/src/lib389/cli/dsctl
3bc51a
+++ b/src/lib389/cli/dsctl
3bc51a
@@ -23,6 +23,7 @@ from lib389.cli_ctl import tls as cli_tls
3bc51a
 from lib389.cli_ctl import health as cli_health
3bc51a
 from lib389.cli_ctl import nsstate as cli_nsstate
3bc51a
 from lib389.cli_ctl import dbgen as cli_dbgen
3bc51a
+from lib389.cli_ctl import dsrc as cli_dsrc
3bc51a
 from lib389.cli_ctl.instance import instance_remove_all
3bc51a
 from lib389.cli_base import (
3bc51a
     disconnect_instance,
3bc51a
@@ -61,6 +62,7 @@ cli_tls.create_parser(subparsers)
3bc51a
 cli_health.create_parser(subparsers)
3bc51a
 cli_nsstate.create_parser(subparsers)
3bc51a
 cli_dbgen.create_parser(subparsers)
3bc51a
+cli_dsrc.create_parser(subparsers)
3bc51a
 
3bc51a
 argcomplete.autocomplete(parser)
3bc51a
 
3bc51a
diff --git a/src/lib389/lib389/cli_ctl/dsrc.py b/src/lib389/lib389/cli_ctl/dsrc.py
3bc51a
new file mode 100644
3bc51a
index 000000000..e49c7f819
3bc51a
--- /dev/null
3bc51a
+++ b/src/lib389/lib389/cli_ctl/dsrc.py
3bc51a
@@ -0,0 +1,312 @@
3bc51a
+# --- BEGIN COPYRIGHT BLOCK ---
3bc51a
+# Copyright (C) 2020 Red Hat, Inc.
3bc51a
+# All rights reserved.
3bc51a
+#
3bc51a
+# License: GPL (version 3 or any later version).
3bc51a
+# See LICENSE for details.
3bc51a
+# --- END COPYRIGHT BLOCK ---
3bc51a
+
3bc51a
+import json
3bc51a
+from os.path import expanduser
3bc51a
+from os import path, remove
3bc51a
+from ldapurl import isLDAPUrl
3bc51a
+from ldap.dn import is_dn
3bc51a
+import configparser
3bc51a
+
3bc51a
+
3bc51a
+def create_dsrc(inst, log, args):
3bc51a
+    """Create the .dsrc file
3bc51a
+
3bc51a
+    [instance]
3bc51a
+    uri = ldaps://hostname:port
3bc51a
+    basedn = dc=example,dc=com
3bc51a
+    binddn = uid=user,....
3bc51a
+    saslmech = [EXTERNAL|PLAIN]
3bc51a
+    tls_cacertdir = /path/to/cacertdir
3bc51a
+    tls_cert = /path/to/user.crt
3bc51a
+    tls_key = /path/to/user.key
3bc51a
+    tls_reqcert = [never, hard, allow]
3bc51a
+    starttls = [true, false]
3bc51a
+    pwdfile = /path/to/file
3bc51a
+    """
3bc51a
+
3bc51a
+    dsrc_file = f'{expanduser("~")}/.dsrc'
3bc51a
+    config = configparser.ConfigParser()
3bc51a
+    config.read(dsrc_file)
3bc51a
+
3bc51a
+    # Verify this section does not already exist
3bc51a
+    instances = config.sections()
3bc51a
+    if inst.serverid in instances:
3bc51a
+        raise ValueError("There is already a configuration section for this instance!")
3bc51a
+
3bc51a
+    # Process and validate the args
3bc51a
+    config[inst.serverid] = {}
3bc51a
+
3bc51a
+    if args.uri is not None:
3bc51a
+        if not isLDAPUrl(args.uri):
3bc51a
+            raise ValueError("The uri is not a valid LDAP URL!")
3bc51a
+        if args.uri.startswith("ldapi"):
3bc51a
+            # We must use EXTERNAL saslmech for LDAPI
3bc51a
+            args.saslmech = "EXTERNAL"
3bc51a
+        config[inst.serverid]['uri'] = args.uri
3bc51a
+    if args.basedn is not None:
3bc51a
+        if not is_dn(args.basedn):
3bc51a
+            raise ValueError("The basedn is not a valid DN!")
3bc51a
+        config[inst.serverid]['basedn'] = args.basedn
3bc51a
+    if args.binddn is not None:
3bc51a
+        if not is_dn(args.binddn):
3bc51a
+            raise ValueError("The binddn is not a valid DN!")
3bc51a
+        config[inst.serverid]['binddn'] = args.binddn
3bc51a
+    if args.saslmech is not None:
3bc51a
+        if args.saslmech not in ['EXTERNAL', 'PLAIN']:
3bc51a
+            raise ValueError("The saslmech must be EXTERNAL or PLAIN!")
3bc51a
+        config[inst.serverid]['saslmech'] = args.saslmech
3bc51a
+    if args.tls_cacertdir is not None:
3bc51a
+        if not path.exists(args.tls_cacertdir):
3bc51a
+            raise ValueError('--tls-cacertdir directory does not exist!')
3bc51a
+        config[inst.serverid]['tls_cacertdir'] = args.tls_cacertdir
3bc51a
+    if args.tls_cert is not None:
3bc51a
+        if not path.exists(args.tls_cert):
3bc51a
+            raise ValueError('--tls-cert does not point to an existing file!')
3bc51a
+        config[inst.serverid]['tls_cert'] = args.tls_cert
3bc51a
+    if args.tls_key is not None:
3bc51a
+        if not path.exists(args.tls_key):
3bc51a
+            raise ValueError('--tls-key does not point to an existing file!')
3bc51a
+        config[inst.serverid]['tls_key'] = args.tls_key
3bc51a
+    if args.tls_reqcert is not None:
3bc51a
+        if args.tls_reqcert not in ['never', 'hard', 'allow']:
3bc51a
+            raise ValueError('--tls-reqcert value is invalid (must be either "never", "allow", or "hard")!')
3bc51a
+        config[inst.serverid]['tls_reqcert'] = args.tls_reqcert
3bc51a
+    if args.starttls:
3bc51a
+         config[inst.serverid]['starttls'] = 'true'
3bc51a
+    if args.pwdfile is not None:
3bc51a
+        if not path.exists(args.pwdfile):
3bc51a
+            raise ValueError('--pwdfile does not exist!')
3bc51a
+        config[inst.serverid]['pwdfile'] = args.pwdfile
3bc51a
+
3bc51a
+    if len(config[inst.serverid]) == 0:
3bc51a
+        # No args set
3bc51a
+        raise ValueError("You must set at least one argument for the new dsrc file!")
3bc51a
+
3bc51a
+    # Print a preview of the config
3bc51a
+    log.info(f'Updating "{dsrc_file}" with:\n')
3bc51a
+    log.info(f'    [{inst.serverid}]')
3bc51a
+    for k, v in config[inst.serverid].items():
3bc51a
+        log.info(f'    {k} = {v}')
3bc51a
+
3bc51a
+    # Perform confirmation?
3bc51a
+    if not args.do_it:
3bc51a
+        while 1:
3bc51a
+            val = input(f'\nUpdate "{dsrc_file}" ? [yes]: ').rstrip().lower()
3bc51a
+            if val == '' or val == 'y' or val == 'yes':
3bc51a
+                break
3bc51a
+            if val == 'n' or val == 'no':
3bc51a
+                return
3bc51a
+
3bc51a
+    # Now write the file
3bc51a
+    with open(dsrc_file, 'w') as configfile:
3bc51a
+        config.write(configfile)
3bc51a
+
3bc51a
+    log.info(f'Successfully updated: {dsrc_file}')
3bc51a
+
3bc51a
+
3bc51a
+def modify_dsrc(inst, log, args):
3bc51a
+    """Modify the instance config
3bc51a
+    """
3bc51a
+    dsrc_file = f'{expanduser("~")}/.dsrc'
3bc51a
+
3bc51a
+    if path.exists(dsrc_file):
3bc51a
+        config = configparser.ConfigParser()
3bc51a
+        config.read(dsrc_file)
3bc51a
+
3bc51a
+        # Verify we have a section to modify
3bc51a
+        instances = config.sections()
3bc51a
+        if inst.serverid not in instances:
3bc51a
+            raise ValueError("There is no configuration section for this instance to modify!")
3bc51a
+
3bc51a
+        # Process and validate the args
3bc51a
+        if args.uri is not None:
3bc51a
+            if not isLDAPUrl(args.uri):
3bc51a
+                raise ValueError("The uri is not a valid LDAP URL!")
3bc51a
+            if args.uri.startswith("ldapi"):
3bc51a
+                # We must use EXTERNAL saslmech for LDAPI
3bc51a
+                args.saslmech = "EXTERNAL"
3bc51a
+            if args.uri == '':
3bc51a
+                del config[inst.serverid]['uri']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['uri'] = args.uri
3bc51a
+        if args.basedn is not None:
3bc51a
+            if not is_dn(args.basedn):
3bc51a
+                raise ValueError("The basedn is not a valid DN!")
3bc51a
+            if args.basedn == '':
3bc51a
+                del config[inst.serverid]['basedn']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['basedn'] = args.basedn
3bc51a
+        if args.binddn is not None:
3bc51a
+            if not is_dn(args.binddn):
3bc51a
+                raise ValueError("The binddn is not a valid DN!")
3bc51a
+            if args.binddn == '':
3bc51a
+                del config[inst.serverid]['binddn']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['binddn'] = args.binddn
3bc51a
+        if args.saslmech is not None:
3bc51a
+            if args.saslmech not in ['EXTERNAL', 'PLAIN']:
3bc51a
+                raise ValueError("The saslmech must be EXTERNAL or PLAIN!")
3bc51a
+            if args.saslmech == '':
3bc51a
+                del config[inst.serverid]['saslmech']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['saslmech'] = args.saslmech
3bc51a
+        if args.tls_cacertdir is not None:
3bc51a
+            if not path.exists(args.tls_cacertdir):
3bc51a
+                raise ValueError('--tls-cacertdir directory does not exist!')
3bc51a
+            if args.tls_cacertdir == '':
3bc51a
+                del config[inst.serverid]['tls_cacertdir']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['tls_cacertdir'] = args.tls_cacertdir
3bc51a
+        if args.tls_cert is not None:
3bc51a
+            if not path.exists(args.tls_cert):
3bc51a
+                raise ValueError('--tls-cert does not point to an existing file!')
3bc51a
+            if args.tls_cert == '':
3bc51a
+                del config[inst.serverid]['tls_cert']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['tls_cert'] = args.tls_cert
3bc51a
+        if args.tls_key is not None:
3bc51a
+            if not path.exists(args.tls_key):
3bc51a
+                raise ValueError('--tls-key does not point to an existing file!')
3bc51a
+            if args.tls_key == '':
3bc51a
+                del config[inst.serverid]['tls_key']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['tls_key'] = args.tls_key
3bc51a
+        if args.tls_reqcert is not None:
3bc51a
+            if args.tls_reqcert not in ['never', 'hard', 'allow']:
3bc51a
+                raise ValueError('--tls-reqcert value is invalid (must be either "never", "allow", or "hard")!')
3bc51a
+            if args.tls_reqcert == '':
3bc51a
+                del config[inst.serverid]['tls_reqcert']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['tls_reqcert'] = args.tls_reqcert
3bc51a
+        if args.starttls:
3bc51a
+             config[inst.serverid]['starttls'] = 'true'
3bc51a
+        if args.cancel_starttls:
3bc51a
+            config[inst.serverid]['starttls'] = 'false'
3bc51a
+        if args.pwdfile is not None:
3bc51a
+            if not path.exists(args.pwdfile):
3bc51a
+                raise ValueError('--pwdfile does not exist!')
3bc51a
+            if args.pwdfile == '':
3bc51a
+                del config[inst.serverid]['pwdfile']
3bc51a
+            else:
3bc51a
+                config[inst.serverid]['pwdfile'] = args.pwdfile
3bc51a
+
3bc51a
+        # Okay now rewrite the file
3bc51a
+        with open(dsrc_file, 'w') as configfile:
3bc51a
+            config.write(configfile)
3bc51a
+
3bc51a
+        log.info(f'Successfully updated: {dsrc_file}')
3bc51a
+    else:
3bc51a
+        raise ValueError(f'There is no .dsrc file "{dsrc_file}" to update!')
3bc51a
+
3bc51a
+
3bc51a
+def delete_dsrc(inst, log, args):
3bc51a
+    """Delete the .dsrc file
3bc51a
+    """
3bc51a
+    dsrc_file = f'{expanduser("~")}/.dsrc'
3bc51a
+    if path.exists(dsrc_file):
3bc51a
+        if not args.do_it:
3bc51a
+            # Get confirmation
3bc51a
+            while 1:
3bc51a
+                val = input(f'\nAre you sure you want to remove this instances configuration ? [no]: ').rstrip().lower()
3bc51a
+                if val == 'y' or val == 'yes':
3bc51a
+                    break
3bc51a
+                if val == '' or val == 'n' or val == 'no':
3bc51a
+                    return
3bc51a
+
3bc51a
+        config = configparser.ConfigParser()
3bc51a
+        config.read(dsrc_file)
3bc51a
+        instances = config.sections()
3bc51a
+        if inst.serverid not in instances:
3bc51a
+            raise ValueError("The is no configuration for this instance")
3bc51a
+
3bc51a
+        # Update the config object
3bc51a
+        del config[inst.serverid]
3bc51a
+
3bc51a
+        if len(config.sections()) == 0:
3bc51a
+            # The file would be empty so just delete it
3bc51a
+            try:
3bc51a
+                remove(dsrc_file)
3bc51a
+                log.info(f'Successfully removed: {dsrc_file}')
3bc51a
+                return
3bc51a
+            except OSError as e:
3bc51a
+                raise ValueError(f'Failed to delete "{dsrc_file}",  error: {str(e)}')
3bc51a
+        else:
3bc51a
+            # write the updated config
3bc51a
+            with open(dsrc_file, 'w') as configfile:
3bc51a
+                config.write(configfile)
3bc51a
+    else:
3bc51a
+        raise ValueError(f'There is no .dsrc file "{dsrc_file}" to update!')
3bc51a
+
3bc51a
+    log.info(f'Successfully updated: {dsrc_file}')
3bc51a
+
3bc51a
+def display_dsrc(inst, log, args):
3bc51a
+    """Display the contents of the ~/.dsrc file
3bc51a
+    """
3bc51a
+    dsrc_file = f'{expanduser("~")}/.dsrc'
3bc51a
+
3bc51a
+    if not path.exists(dsrc_file):
3bc51a
+        raise ValueError(f'There is no dsrc file "{dsrc_file}" to display!')
3bc51a
+
3bc51a
+    config = configparser.ConfigParser()
3bc51a
+    config.read(dsrc_file)
3bc51a
+    instances = config.sections()
3bc51a
+
3bc51a
+    for inst_section in instances:
3bc51a
+        if args.json:
3bc51a
+            log.info(json.dumps({inst_section: dict(config[inst_section])}, indent=4))
3bc51a
+        else:
3bc51a
+            log.info(f'[{inst_section}]')
3bc51a
+            for k, v in config[inst_section].items():
3bc51a
+                log.info(f'{k} = {v}')
3bc51a
+            log.info("")
3bc51a
+
3bc51a
+
3bc51a
+def create_parser(subparsers):
3bc51a
+    dsrc_parser = subparsers.add_parser('dsrc', help="Manage the .dsrc file")
3bc51a
+    subcommands = dsrc_parser.add_subparsers(help="action")
3bc51a
+
3bc51a
+    # Create .dsrc file
3bc51a
+    dsrc_create_parser = subcommands.add_parser('create', help='Generate the .dsrc file')
3bc51a
+    dsrc_create_parser.set_defaults(func=create_dsrc)
3bc51a
+    dsrc_create_parser.add_argument('--uri', help="The URI (LDAP URL) for the Directory Server instance.")
3bc51a
+    dsrc_create_parser.add_argument('--basedn', help="The default database suffix.")
3bc51a
+    dsrc_create_parser.add_argument('--binddn', help="The default Bind DN used or authentication.")
3bc51a
+    dsrc_create_parser.add_argument('--saslmech', help="The SASL mechanism to use: PLAIN or EXTERNAL.")
3bc51a
+    dsrc_create_parser.add_argument('--tls-cacertdir', help="The directory containing the Trusted Certificate Authority certificate.")
3bc51a
+    dsrc_create_parser.add_argument('--tls-cert', help="The absolute file name to the server certificate.")
3bc51a
+    dsrc_create_parser.add_argument('--tls-key', help="The absolute file name to the server certificate key.")
3bc51a
+    dsrc_create_parser.add_argument('--tls-reqcert', help="Request certificate strength: 'never', 'allow', 'hard'")
3bc51a
+    dsrc_create_parser.add_argument('--starttls', action='store_true', help="Use startTLS for connection to the server.")
3bc51a
+    dsrc_create_parser.add_argument('--pwdfile', help="The absolute path to a file containing the Bind DN's password.")
3bc51a
+    dsrc_create_parser.add_argument('--do-it', action='store_true', help="Create the file without any confirmation.")
3bc51a
+
3bc51a
+    dsrc_modify_parser = subcommands.add_parser('modify', help='Modify the .dsrc file')
3bc51a
+    dsrc_modify_parser.set_defaults(func=modify_dsrc)
3bc51a
+    dsrc_modify_parser.add_argument('--uri', nargs='?', const='', help="The URI (LDAP URL) for the Directory Server instance.")
3bc51a
+    dsrc_modify_parser.add_argument('--basedn', nargs='?', const='', help="The default database suffix.")
3bc51a
+    dsrc_modify_parser.add_argument('--binddn', nargs='?', const='', help="The default Bind DN used or authentication.")
3bc51a
+    dsrc_modify_parser.add_argument('--saslmech', nargs='?', const='', help="The SASL mechanism to use: PLAIN or EXTERNAL.")
3bc51a
+    dsrc_modify_parser.add_argument('--tls-cacertdir', nargs='?', const='', help="The directory containing the Trusted Certificate Authority certificate.")
3bc51a
+    dsrc_modify_parser.add_argument('--tls-cert', nargs='?', const='', help="The absolute file name to the server certificate.")
3bc51a
+    dsrc_modify_parser.add_argument('--tls-key', nargs='?', const='', help="The absolute file name to the server certificate key.")
3bc51a
+    dsrc_modify_parser.add_argument('--tls-reqcert', nargs='?', const='', help="Request certificate strength: 'never', 'allow', 'hard'")
3bc51a
+    dsrc_modify_parser.add_argument('--starttls', action='store_true', help="Use startTLS for connection to the server.")
3bc51a
+    dsrc_modify_parser.add_argument('--cancel-starttls', action='store_true', help="Do not use startTLS for connection to the server.")
3bc51a
+    dsrc_modify_parser.add_argument('--pwdfile', nargs='?', const='', help="The absolute path to a file containing the Bind DN's password.")
3bc51a
+    dsrc_modify_parser.add_argument('--do-it', action='store_true', help="Update the file without any confirmation.")
3bc51a
+
3bc51a
+    # Delete the instance from the .dsrc file
3bc51a
+    dsrc_delete_parser = subcommands.add_parser('delete', help='Delete instance configuration from the .dsrc file.')
3bc51a
+    dsrc_delete_parser.set_defaults(func=delete_dsrc)
3bc51a
+    dsrc_delete_parser.add_argument('--do-it', action='store_true',
3bc51a
+                                    help="Delete this instance's configuration from the .dsrc file.")
3bc51a
+
3bc51a
+    # Display .dsrc file
3bc51a
+    dsrc_display_parser = subcommands.add_parser('display', help='Display the contents of the .dsrc file.')
3bc51a
+    dsrc_display_parser.set_defaults(func=display_dsrc)
3bc51a
-- 
3bc51a
2.26.2
3bc51a