From 664d6b2e62b7f49c38291820ee019a2f1ec9302e Mon Sep 17 00:00:00 2001 From: William Brown Date: Sun, 8 Jan 2017 20:31:44 +1000 Subject: [PATCH] Ticket 49084 - Configshell for cli tools. Bug Description: We want to improve the quality of our cli and admin tools. By using a tool like configshell, we can quickly create powerful and usable command line tools. Fix Description: Convert dsadm to use configshell as a demo, move the argparse based tool to the side. https://fedorahosted.org/389/ticket/49084 Author: wibrown Review by: ??? --- cli/dsadm | 171 +++++++++++++++++++++------------------ cli/dsadm-orig | 104 ++++++++++++++++++++++++ examples/ds-setup-rest-admin.inf | 48 +++++++++++ lib389/__init__.py | 32 ++++---- lib389/cli_adm/instance.py | 40 ++++++++- lib389/instance/options.py | 2 +- setup.py | 1 + 7 files changed, 301 insertions(+), 97 deletions(-) create mode 100755 cli/dsadm-orig create mode 100644 examples/ds-setup-rest-admin.inf diff --git a/cli/dsadm b/cli/dsadm index 77262f0..1270312 100755 --- a/cli/dsadm +++ b/cli/dsadm @@ -8,97 +8,108 @@ # See LICENSE for details. # --- END COPYRIGHT BLOCK --- -import argparse import logging import sys +import time + +import configshell_fb as configshell -from lib389.cli_base import _get_arg from lib389 import DirSrv -from lib389.cli_adm import instance as cli_instance -from lib389.cli_base import disconnect_instance +from lib389.instance.setup import SetupDs +from lib389._constants import * +from lib389.cli_adm.instance import DsadmInstance logging.basicConfig() log = logging.getLogger("dsadm") -if __name__ == '__main__': - - parser = argparse.ArgumentParser() - - parser.add_argument('-v', '--verbose', - help="Display verbose operation tracing during command execution", - action='store_true', default=False - ) - # Should this actually be in the sub modules? That way they can set the requirements. - parser.add_argument('-Z', '--instance', - help="The name of the instance to act upon", - default=None, - ) - - subparsers = parser.add_subparsers(help="resources to act upon") +verbose = False + +def create_warning(): + print(""" + _________________________________________ +/ This is not what you want! Press ctrl-c \\ +\ now ... / + ----------------------------------------- + \\ / \\ //\\ + \\ |\\___/| / \\// \\\\ + /0 0 \\__ / // | \\ \\ + / / \\/_/ // | \\ \\ + @_^_@'/ \\/_ // | \\ \\ + //_^_/ \\/_ // | \\ \\ + ( //) | \\/// | \\ \\ + ( / /) _|_ / ) // | \\ _\\ + ( // /) '/,_ _ _/ ( ; -. | _ _\\.-~ .-~~~^-. + (( / / )) ,-{ _ `-.|.-~-. .~ `. + (( // / )) '/\\ / ~-. _ .-~ .-~^-. \\ + (( /// )) `. { } / \\ \\ + (( / )) .----~-.\\ \\-' .~ \\ `. \\^-. + ///.----..> \\ _ -~ `. ^-` ^-_ + ///-._ _ _ _ _ _ _}^ - - - - ~ ~-- ,.-~ + /.-~ + """) + for i in range(1,6): + log.info('%s ...' % (5 - int(i))) + time.sleep(1) + print('Launching ...') + +class DsadmRoot(configshell.node.ConfigNode): + def __init__(self, shell): + configshell.node.ConfigNode.__init__(self, '/', shell=shell) + # For each instance, attach it here. + self.anon_instance = DirSrv(verbose=verbose) + self.refresh() + + def refresh(self): + # List all instances + # How do we clean out existing instances? + print('refreshing instances...') + instances = self.anon_instance.list(all=True) + print(instances) + for inst in instances: + DsadmInstance(instance=inst, parent=self, verbose=verbose) + + def ui_command_create(self): + """ + create - Create a new instance of Directory Server interactively + """ + create_warning() + print('create') + + def ui_command_create_from(self, file, dryrun=False, containerised=False, isolemnlyswearthatIamuptonogood=False): + """ + create_from - Create a new instance of directory server from the file requested. + """ + if isolemnlyswearthatIamuptonogood is False: + print('Are you sure you want this? This is pre-release software ... check help for details.') + return + create_warning() + ### NEED TO CONVERT TO BOOLS + if containerised is not False: + # Convert to boolean + containerised = True + log.debug("Containerised features requested.") + sd = SetupDs(verbose, dryrun, log, containerised) + if sd.create_from_inf(file): + print("Sucessfully created instance") + else: + print("Failed to create instance") + self.refresh() + + def ui_command_create_example_file(self): + """ + create_example_file - Write the setup-example.inf to the current directory. + """ + print("example") - # We stack our needed options in via submodules. - cli_instance.create_parser(subparsers) - - # Then we tell it to execute. - - args = parser.parse_args() +if __name__ == '__main__': - if args.verbose: - log.setLevel(logging.DEBUG) + shell = configshell.shell.ConfigShell("~/.dsadm") + root_node = DsadmRoot(shell) + if len(sys.argv) > 1: + shell.run_cmdline(" ".join(sys.argv[1:])) + sys.exit(0) else: - log.setLevel(logging.INFO) - - log.debug("The 389 Directory Server Administration Tool") - # Leave this comment here: UofA let me take this code with me provided - # I gave attribution. -- wibrown - log.debug("Inspired by works of: ITS, The University of Adelaide") + shell.run_interactive() - log.debug("Called with: %s", args) - - # Assert we have a resources to work on. - if not hasattr(args, 'func'): - log.error("No resource provided to act upon") - log.error("USAGE: dsadm [options] [action options]") - sys.exit(1) - - # Connect - # inst = None - inst = DirSrv(verbose=args.verbose) - - result = True - - # we allocate an instance in all cases unless we are CREATING the server. - if not (hasattr(args, 'noinst') and args.noinst is True): - # Get the instance name if needed. - inst_id = _get_arg( args.instance, msg="Directory Server instance name") - - # Allocate the instance based on name - insts = inst.list(serverid=inst_id) - if len(insts) != 1: - raise ValueError("No such instance %s" % inst_id) - - inst.allocate(insts[0]) - log.debug('Instance allocated') - - if args.verbose: - result = args.func(inst, log, args) - else: - try: - result = args.func(inst, log, args) - except Exception as e: - log.debug(e, exc_info=True) - log.error("Error: %s" % e.message) - disconnect_instance(inst) - - if result is True: - log.info('FINISH: Command succeeded') - elif result is False: - log.info('FAIL: Command failed. See output for details.') - - # Done! - log.debug("dsadm is brought to you by the letter R and the number 27.") - - if result is False: - sys.exit(1) diff --git a/cli/dsadm-orig b/cli/dsadm-orig new file mode 100755 index 0000000..77262f0 --- /dev/null +++ b/cli/dsadm-orig @@ -0,0 +1,104 @@ +#!/usr/bin/python3 + +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2016 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +import argparse +import logging +import sys + +from lib389.cli_base import _get_arg +from lib389 import DirSrv +from lib389.cli_adm import instance as cli_instance +from lib389.cli_base import disconnect_instance + +logging.basicConfig() +log = logging.getLogger("dsadm") + +if __name__ == '__main__': + + parser = argparse.ArgumentParser() + + parser.add_argument('-v', '--verbose', + help="Display verbose operation tracing during command execution", + action='store_true', default=False + ) + # Should this actually be in the sub modules? That way they can set the requirements. + parser.add_argument('-Z', '--instance', + help="The name of the instance to act upon", + default=None, + ) + + subparsers = parser.add_subparsers(help="resources to act upon") + + # We stack our needed options in via submodules. + + cli_instance.create_parser(subparsers) + + # Then we tell it to execute. + + args = parser.parse_args() + + if args.verbose: + log.setLevel(logging.DEBUG) + else: + log.setLevel(logging.INFO) + + log.debug("The 389 Directory Server Administration Tool") + # Leave this comment here: UofA let me take this code with me provided + # I gave attribution. -- wibrown + log.debug("Inspired by works of: ITS, The University of Adelaide") + + log.debug("Called with: %s", args) + + # Assert we have a resources to work on. + if not hasattr(args, 'func'): + log.error("No resource provided to act upon") + log.error("USAGE: dsadm [options] [action options]") + sys.exit(1) + + # Connect + # inst = None + inst = DirSrv(verbose=args.verbose) + + result = True + + # we allocate an instance in all cases unless we are CREATING the server. + if not (hasattr(args, 'noinst') and args.noinst is True): + # Get the instance name if needed. + inst_id = _get_arg( args.instance, msg="Directory Server instance name") + + # Allocate the instance based on name + insts = inst.list(serverid=inst_id) + if len(insts) != 1: + raise ValueError("No such instance %s" % inst_id) + + inst.allocate(insts[0]) + log.debug('Instance allocated') + + if args.verbose: + result = args.func(inst, log, args) + else: + try: + result = args.func(inst, log, args) + except Exception as e: + log.debug(e, exc_info=True) + log.error("Error: %s" % e.message) + disconnect_instance(inst) + + if result is True: + log.info('FINISH: Command succeeded') + elif result is False: + log.info('FAIL: Command failed. See output for details.') + + # Done! + log.debug("dsadm is brought to you by the letter R and the number 27.") + + if result is False: + sys.exit(1) + diff --git a/examples/ds-setup-rest-admin.inf b/examples/ds-setup-rest-admin.inf new file mode 100644 index 0000000..4964a03 --- /dev/null +++ b/examples/ds-setup-rest-admin.inf @@ -0,0 +1,48 @@ +; --- BEGIN COPYRIGHT BLOCK --- +; Copyright (C) 2015 Red Hat, Inc. +; All rights reserved. +; +; License: GPL (version 3 or any later version). +; See LICENSE for details. +; --- END COPYRIGHT BLOCK --- + +; Author: firstyear at redhat.com + +; This is a version 2 ds setup inf file. +; It is used by the python versions of setup-ds-* +; Most options map 1 to 1 to the original .inf file. +; However, there are some differences that I envision +; For example, note the split backend section. +; You should be able to create, one, many or no backends in an install + +[general] +config_version=2 +full_machine_name=localhost.localdomain +strict_host_checking=False + +[slapd] +instance_name=localhost +user=dirsrv +group=dirsrv +port=389 +secure_port=636 +root_dn=cn=Directory Manager +root_password=password +prefix=/opt/dirsrv +defaults=latest + +[rest] +user=dirsrv_rest +group=dirsrv_rest +port=5000 +;rest389_path +;lib389_path + +[backend-userRoot] +suffix=dc=example,dc=com +; this is controlled by slapd.InstallLdifFile == none, suggest or path in setup-ds.pl +sample_entries=yes + + + + diff --git a/lib389/__init__.py b/lib389/__init__.py index 0ce0463..e4ab9af 100644 --- a/lib389/__init__.py +++ b/lib389/__init__.py @@ -520,21 +520,22 @@ class DirSrv(SimpleLDAPObject, object): self.binddn = args.get(SER_ROOT_DN, DN_DM) self.bindpw = args.get(SER_ROOT_PW, PW_DM) self.creation_suffix = args.get(SER_CREATION_SUFFIX, DEFAULT_SUFFIX) - self.userid = args.get(SER_USER_ID) - if not self.userid: - if os.getuid() == 0: - # as root run as default user - self.userid = DEFAULT_USER - else: - self.userid = pwd.getpwuid(os.getuid())[0] - - # Settings from args of server attributes - 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) - # Allocate from the args, or use our env, or use / - if args.get(SER_DEPLOYED_DIR, self.prefix) is not None: - self.prefix = args.get(SER_DEPLOYED_DIR, self.prefix) + # These settings are only needed for a local connection. + if self.isLocal: + self.userid = args.get(SER_USER_ID, None) + if not self.userid: + if os.getuid() == 0: + # as root run as default user + self.userid = DEFAULT_USER + else: + self.userid = pwd.getpwuid(os.getuid())[0] + # Settings from args of server attributes + 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) + # Allocate from the args, or use our env, or use / + if args.get(SER_DEPLOYED_DIR, self.prefix) is not None: + self.prefix = args.get(SER_DEPLOYED_DIR, self.prefix) self.realm = args.get(SER_REALM, None) if self.realm is not None: self.krb5_realm = MitKrb5(realm=self.realm, debug=self.verbose) @@ -1125,6 +1126,7 @@ class DirSrv(SimpleLDAPObject, object): log.error("ASAN options will be copied from your environment") env['ASAN_SYMBOLIZER_PATH'] = "/usr/bin/llvm-symbolizer" env['ASAN_OPTIONS'] = "symbolize=1 detect_deadlocks=1 log_path=%s/ns-slapd-%s.asan" % (self.ds_paths.run_dir, self.serverid) + env['LSAN_OPTIONS'] = "report_objects=1" env.update(os.environ) subprocess.check_call(["%s/ns-slapd" % self.get_sbin_dir(), "-D", diff --git a/lib389/cli_adm/instance.py b/lib389/cli_adm/instance.py index 1d44e3c..6c3722d 100644 --- a/lib389/cli_adm/instance.py +++ b/lib389/cli_adm/instance.py @@ -6,17 +6,55 @@ # See LICENSE for details. # --- END COPYRIGHT BLOCK --- +from lib389 import DirSrv from lib389._constants import * - from lib389.tools import DirSrvTools from lib389.instance.setup import SetupDs from getpass import getpass import os import time import sys +import configshell_fb as configshell from lib389.instance.options import General2Base, Slapd2Base +class DsadmInstance(configshell.node.ConfigNode): + """ + Abstract instance class, do not instantiate directly. + """ + def __init__(self, instance, parent=None, verbose=False): + configshell.node.ConfigNode.__init__(self, instance[CONF_SERVER_ID], parent) + # Stash our DirSrv instance somehow based on name? + self._inst = DirSrv(verbose=verbose) + self._inst.allocate(instance) + + def ui_command_start(self): + """ + start - start the instance of directory server + """ + self._inst.start() + + def ui_command_stop(self): + """ + stop - stop the instance of directory server + """ + self._inst.stop() + + def ui_command_restart(self): + """ + restart - restart the instance of directory server + """ + self._inst.restart() + + def ui_command_status(self): + """ + status - status of the instance of directory server + """ + if self._inst.status(): + print("Instance is running") + else: + print("Instance is not running") + def instance_list(inst, log, args): instances = inst.list(all=True) diff --git a/lib389/instance/options.py b/lib389/instance/options.py index 21d8c27..b1a5418 100644 --- a/lib389/instance/options.py +++ b/lib389/instance/options.py @@ -242,7 +242,7 @@ class Slapd2Base(Options2): def _format(self, d): new_d = {} ks = d.keys() - no_format_keys = ks - format_keys + no_format_keys = set(ks) - set(format_keys) for k in no_format_keys: new_d[k] = d[k] diff --git a/setup.py b/setup.py index 0582b25..21755c0 100644 --- a/setup.py +++ b/setup.py @@ -51,6 +51,7 @@ setup( # find lib389/clitools -name ds\* -exec echo \''{}'\', \; data_files=[ + ('/usr/share/lib389/examples/', ['examples/ds-setup-rest-admin.inf']), ('/usr/sbin/', [ # 'lib389/clitools/ds_setup', 'cli/dsadm', -- 1.8.3.1