From f00b94f18e1d82d1488cbcee6053f03423bc2f49 Mon Sep 17 00:00:00 2001 From: Barry Warsaw Date: Sat, 22 Jul 2017 03:02:05 +0000 Subject: Convert to click for CLI options --- src/mailman/commands/cli_control.py | 276 ++++++++++++++++++------------------ 1 file changed, 137 insertions(+), 139 deletions(-) (limited to 'src/mailman/commands/cli_control.py') diff --git a/src/mailman/commands/cli_control.py b/src/mailman/commands/cli_control.py index bf10ff017..6484ab63f 100644 --- a/src/mailman/commands/cli_control.py +++ b/src/mailman/commands/cli_control.py @@ -19,6 +19,7 @@ import os import sys +import click import errno import signal import logging @@ -27,6 +28,7 @@ from mailman.bin.master import WatcherState, master_state from mailman.config import config from mailman.core.i18n import _ from mailman.interfaces.command import ICLISubCommand +from mailman.utilities.options import I18nCommand from public import public from zope.interface import implementer @@ -34,112 +36,94 @@ from zope.interface import implementer qlog = logging.getLogger('mailman.runner') +@click.command( + cls=I18nCommand, + help=_('Start the Mailman master and runner processes.')) +@click.option( + '--force', '-f', + is_flag=True, default=False, + help=_("""\ + If the master watcher finds an existing master lock, it will normally exit + with an error message. With this option, the master will perform an extra + level of checking. If a process matching the host/pid described in the + lock file is running, the master will still exit, requiring you to manually + clean up the lock. But if no matching process is found, the master will + remove the apparently stale lock and make another attempt to claim the + master lock.""")) +@click.option( + '--run-as-user', '-u', + is_flag=True, default=True, + help=_("""\ + Normally, this script will refuse to run if the user id and group id are + not set to the 'mailman' user and group (as defined when you configured + Mailman). If run as root, this script will change to this user and group + before the check is made. + + This can be inconvenient for testing and debugging purposes, so the -u flag + means that the step that sets and checks the uid/gid is skipped, and the + program is run as the current user and group. This flag is not recommended + for normal production environments. + + Note though, that if you run with -u and are not in the mailman group, you + may have permission problems, such as being unable to delete a list's + archives through the web. Tough luck!""")) +@click.option( + '--quiet', '-q', + is_flag=True, default=False, + help=_("""\ + Don't print status messages. Error messages are still printed to standard + error.""")) +@click.pass_context +def start(ctx, force, run_as_user, quiet): + # Although there's a potential race condition here, it's a better user + # experience for the parent process to refuse to start twice, rather than + # having it try to start the master, which will error exit. + status, lock = master_state() + if status is WatcherState.conflict: + ctx.fail(_('GNU Mailman is already running')) + elif status in (WatcherState.stale_lock, WatcherState.host_mismatch): + if not force: + ctx.fail( + _('A previous run of GNU Mailman did not exit ' + 'cleanly ({}). Try using --force'.format(status.name))) + # Daemon process startup according to Stevens, Advanced Programming in the + # UNIX Environment, Chapter 13. + pid = os.fork() + if pid: + # parent + if not quiet: + print(_("Starting Mailman's master runner")) + return + # child: Create a new session and become the session leader, but since we + # won't be opening any terminal devices, don't do the ultra-paranoid + # suggestion of doing a second fork after the setsid() call. + os.setsid() + # Instead of cd'ing to root, cd to the Mailman runtime directory. However, + # before we do that, set an environment variable used by the subprocesses + # to calculate their path to the $VAR_DIR. + os.environ['MAILMAN_VAR_DIR'] = config.VAR_DIR + os.chdir(config.VAR_DIR) + # Exec the master watcher. + execl_args = [ + sys.executable, sys.executable, + os.path.join(config.BIN_DIR, 'master'), + ] + if force: + execl_args.append('--force') + # Always pass the configuration file path to the master process, so there's + # no confusion about which one is being used. + execl_args.extend(['-C', config.filename]) + qlog.debug('starting: %s', execl_args) + os.execl(*execl_args) + # We should never get here. + raise RuntimeError('os.execl() failed') + + @public @implementer(ICLISubCommand) class Start: - """Start the Mailman master and runner processes.""" - name = 'start' - - def add(self, parser, command_parser): - """See `ICLISubCommand`.""" - self.parser = parser - command_parser.add_argument( - '-f', '--force', - default=False, action='store_true', - help=_("""\ - If the master watcher finds an existing master lock, it will - normally exit with an error message. With this option, the master - will perform an extra level of checking. If a process matching - the host/pid described in the lock file is running, the master - will still exit, requiring you to manually clean up the lock. But - if no matching process is found, the master will remove the - apparently stale lock and make another attempt to claim the master - lock.""")) - command_parser.add_argument( - '-u', '--run-as-user', - default=True, action='store_false', - help=_("""\ - Normally, this script will refuse to run if the user id and group - id are not set to the 'mailman' user and group (as defined when - you configured Mailman). If run as root, this script will change - to this user and group before the check is made. - - This can be inconvenient for testing and debugging purposes, so - the -u flag means that the step that sets and checks the uid/gid - is skipped, and the program is run as the current user and group. - This flag is not recommended for normal production environments. - - Note though, that if you run with -u and are not in the mailman - group, you may have permission problems, such as being unable to - delete a list's archives through the web. Tough luck!""")) - command_parser.add_argument( - '-q', '--quiet', - default=False, action='store_true', - help=_("""\ - Don't print status messages. Error messages are still printed to - standard error.""")) - - def process(self, args): - """See `ICLISubCommand`.""" - # Although there's a potential race condition here, it's a better user - # experience for the parent process to refuse to start twice, rather - # than having it try to start the master, which will error exit. - status, lock = master_state() - if status is WatcherState.conflict: - self.parser.error(_('GNU Mailman is already running')) - elif status in (WatcherState.stale_lock, WatcherState.host_mismatch): - if args.force is None: - self.parser.error( - _('A previous run of GNU Mailman did not exit ' - 'cleanly. Try using --force.')) - def log(message): # noqa: E306 - if not args.quiet: - print(message) - # Try to find the path to a valid, existing configuration file, and - # refuse to start if one cannot be found. - if args.config is not None: - config_path = args.config - elif config.filename is not None: - config_path = config.filename - else: - config_path = os.path.join(config.VAR_DIR, 'etc', 'mailman.cfg') - if not os.path.exists(config_path): - print(_("""\ -No valid configuration file could be found, so Mailman will refuse to start. -Use -C/--config to specify a valid configuration file."""), file=sys.stderr) - sys.exit(1) - # Daemon process startup according to Stevens, Advanced Programming in - # the UNIX Environment, Chapter 13. - pid = os.fork() - if pid: - # parent - log(_("Starting Mailman's master runner")) - return - # child: Create a new session and become the session leader, but since - # we won't be opening any terminal devices, don't do the - # ultra-paranoid suggestion of doing a second fork after the setsid() - # call. - os.setsid() - # Instead of cd'ing to root, cd to the Mailman runtime directory. - # However, before we do that, set an environment variable used by the - # subprocesses to calculate their path to the $VAR_DIR. - os.environ['MAILMAN_VAR_DIR'] = config.VAR_DIR - os.chdir(config.VAR_DIR) - # Exec the master watcher. - execl_args = [ - sys.executable, sys.executable, - os.path.join(config.BIN_DIR, 'master'), - ] - if args.force: - execl_args.append('--force') - # Always pass the config file path to the master projects, so there's - # no confusion about which cfg is being used. - execl_args.extend(['-C', config_path]) - qlog.debug('starting: %s', execl_args) - os.execl(*execl_args) - # We should never get here. - raise RuntimeError('os.execl() failed') + command = start def kill_watcher(sig): @@ -163,53 +147,67 @@ def kill_watcher(sig): os.unlink(config.PID_FILE) -@implementer(ICLISubCommand) -class SignalCommand: - """Common base class for simple, signal sending commands.""" - - name = None - message = None - signal = None - - def add(self, parser, command_parser): - """See `ICLISubCommand`.""" - command_parser.add_argument( - '-q', '--quiet', - default=False, action='store_true', - help=_("""\ - Don't print status messages. Error messages are still printed to - standard error.""")) - - def process(self, args): - """See `ICLISubCommand`.""" - if not args.quiet: - print(_(self.message)) - kill_watcher(self.signal) +@click.command( + cls=I18nCommand, + help=_('Stop the Mailman master and runner processes.')) +@click.option( + '--quiet', '-q', + is_flag=True, default=False, + help=_("""\ + Don't print status messages. Error messages are still printed to standard + error.""")) +def stop(quiet): + if not quiet: + print(_("Shutting down Mailman's master runner")) + kill_watcher(signal.SIGTERM) @public -class Stop(SignalCommand): - """Stop the Mailman master and runner processes.""" - +@implementer(ICLISubCommand) +class Stop: name = 'stop' - message = _("Shutting down Mailman's master runner") - signal = signal.SIGTERM + command = stop -@public -class Reopen(SignalCommand): - """Signal the Mailman processes to re-open their log files.""" +@click.command( + cls=I18nCommand, + help=_('Signal the Mailman processes to re-open their log files.')) +@click.option( + '--quiet', '-q', + is_flag=True, default=False, + help=_("""\ + Don't print status messages. Error messages are still printed to standard + error.""")) +def reopen(quiet): + if not quiet: + print(_('Reopening the Mailman runners')) + kill_watcher(signal.SIGHUP) + +@public +@implementer(ICLISubCommand) +class Reopen: name = 'reopen' - message = _('Reopening the Mailman runners') - signal = signal.SIGHUP + command = reopen + + +@click.command( + cls=I18nCommand, + help=_('Stop and restart the Mailman runner subprocesses.')) +@click.option( + '--quiet', '-q', + is_flag=True, default=False, + help=_("""\ + Don't print status messages. Error messages are still printed to standard + error.""")) +def restart(quiet): + if not quiet: + print(_('Restarting the Mailman runners')) + kill_watcher(signal.SIGUSR1) @public @implementer(ICLISubCommand) -class Restart(SignalCommand): - """Stop and restart the Mailman runner subprocesses.""" - +class Restart: name = 'restart' - message = _('Restarting the Mailman runners') - signal = signal.SIGUSR1 + command = restart -- cgit v1.2.3-70-g09d2