--- a/VirtualMailManager/cli/subcommands.py Fri Dec 21 12:28:56 2012 +0000
+++ b/VirtualMailManager/cli/subcommands.py Sun Dec 30 16:10:29 2012 +0000
@@ -9,150 +9,83 @@
"""
import locale
-import os
+import platform
+from argparse import Action, ArgumentParser, ArgumentTypeError, \
+ RawDescriptionHelpFormatter
from textwrap import TextWrapper
from time import strftime, strptime
from VirtualMailManager import ENCODING
-from VirtualMailManager.cli import get_winsize, prog, w_err, w_std
-from VirtualMailManager.cli.clihelp import help_msgs
+from VirtualMailManager.cli import get_winsize, w_err, w_std
from VirtualMailManager.common import human_size, size_in_bytes, \
version_str, format_domain_default
from VirtualMailManager.constants import __copyright__, __date__, \
__version__, ACCOUNT_EXISTS, ALIAS_EXISTS, ALIASDOMAIN_ISDOMAIN, \
- DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, EX_MISSING_ARGS, \
- RELOCATED_EXISTS, TYPE_ACCOUNT, TYPE_ALIAS, TYPE_RELOCATED
+ DOMAIN_ALIAS_EXISTS, INVALID_ARGUMENT, RELOCATED_EXISTS, TYPE_ACCOUNT, \
+ TYPE_ALIAS, TYPE_RELOCATED
from VirtualMailManager.errors import VMMError
from VirtualMailManager.password import list_schemes
from VirtualMailManager.serviceset import SERVICES
__all__ = (
- 'Command', 'RunContext', 'cmd_map', 'usage', 'alias_add', 'alias_delete',
- 'alias_info', 'aliasdomain_add', 'aliasdomain_delete', 'aliasdomain_info',
- 'aliasdomain_switch', 'catchall_add', 'catchall_info', 'catchall_delete',
- 'config_get', 'config_set', 'configure',
- 'domain_add', 'domain_delete', 'domain_info', 'domain_quota',
- 'domain_services', 'domain_transport', 'domain_note', 'get_user', 'help_',
- 'list_domains', 'list_pwschemes', 'list_users', 'list_aliases',
- 'list_relocated', 'list_addresses', 'relocated_add', 'relocated_delete',
- 'relocated_info', 'user_add', 'user_delete', 'user_info', 'user_name',
- 'user_password', 'user_quota', 'user_services', 'user_transport',
- 'user_note', 'version',
+ 'RunContext', 'alias_add', 'alias_delete', 'alias_info', 'aliasdomain_add',
+ 'aliasdomain_delete', 'aliasdomain_info', 'aliasdomain_switch',
+ 'catchall_add', 'catchall_delete', 'catchall_info', 'config_get',
+ 'config_set', 'configure', 'domain_add', 'domain_delete', 'domain_info',
+ 'domain_note', 'domain_quota', 'domain_services', 'domain_transport',
+ 'get_user', 'list_addresses', 'list_aliases', 'list_domains',
+ 'list_pwschemes', 'list_relocated', 'list_users', 'relocated_add',
+ 'relocated_delete', 'relocated_info', 'setup_parser', 'user_add',
+ 'user_delete', 'user_info', 'user_name', 'user_note', 'user_password',
+ 'user_quota', 'user_services', 'user_transport',
)
+WS_ROWS = get_winsize()[1] - 2
+
_ = lambda msg: msg
-txt_wrpr = TextWrapper(width=get_winsize()[1] - 1)
-cmd_map = {}
-
-
-class Command(object):
- """Container class for command information."""
- __slots__ = ('name', 'alias', 'func', 'args', 'descr')
- FMT_HLP_USAGE = """
-usage: %(prog)s %(name)s %(args)s
- %(prog)s %(alias)s %(args)s
-"""
-
- def __init__(self, name, alias, func, args, descr):
- """Create a new Command instance.
-
- Arguments:
-
- `name` : str
- the command name, e.g. ``addalias``
- `alias` : str
- the command's short alias, e.g. ``aa``
- `func` : callable
- the function to handle the command
- `args` : str
- argument placeholders, e.g. ``aliasaddress``
- `descr` : str
- short description of the command
- """
- self.name = name
- self.alias = alias
- self.func = func
- self.args = args
- self.descr = descr
-
- @property
- def usage(self):
- """the command's usage info."""
- return '%s %s %s' % (prog, self.name, self.args)
-
- def help_(self):
- """Print the Command's help message to stdout."""
- old_ii = txt_wrpr.initial_indent
- old_si = txt_wrpr.subsequent_indent
-
- txt_wrpr.subsequent_indent = (len(self.name) + 2) * ' '
- w_std(txt_wrpr.fill('%s: %s' % (self.name, self.descr)))
-
- info = Command.FMT_HLP_USAGE % dict(alias=self.alias, args=self.args,
- name=self.name, prog=prog)
- w_std(info)
-
- txt_wrpr.initial_indent = txt_wrpr.subsequent_indent = ' '
- try:
- [w_std(txt_wrpr.fill(_(para)) + '\n') for para
- in help_msgs[self.name]]
- except KeyError:
- w_err(1, _("Subcommand '%s' is not yet documented." % self.name),
- 'see also: vmm(1)')
+txt_wrpr = TextWrapper(width=WS_ROWS)
class RunContext(object):
"""Contains all information necessary to run a subcommand."""
- __slots__ = ('argc', 'args', 'cget', 'hdlr', 'scmd')
+ __slots__ = ('args', 'cget', 'hdlr')
plan_a_b = _('Plan A failed ... trying Plan B: %(subcommand)s %(object)s')
- def __init__(self, argv, handler, command):
+ def __init__(self, args, handler):
"""Create a new RunContext"""
- self.argc = len(argv)
- self.args = argv[:] # will be moved to argparse
+ self.args = args
self.cget = handler.cfg_dget
self.hdlr = handler
- self.scmd = command
def alias_add(ctx):
"""create a new alias e-mail address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing alias address and destination.'),
- ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing destination address.'), ctx.scmd)
- ctx.hdlr.alias_add(ctx.args[2].lower(), *ctx.args[3:])
+ ctx.hdlr.alias_add(ctx.args.address.lower(), *ctx.args.destination)
def alias_delete(ctx):
"""delete the specified alias e-mail address or one of its destinations"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing alias address.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.alias_delete(ctx.args[2].lower())
- else:
- ctx.hdlr.alias_delete(ctx.args[2].lower(), ctx.args[3:])
+ destination = ctx.args.destination if ctx.args.destination else None
+ ctx.hdlr.alias_delete(ctx.args.address.lower(), destination)
def alias_info(ctx):
"""show the destination(s) of the specified alias"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing alias address.'), ctx.scmd)
- address = ctx.args[2].lower()
+ address = ctx.args.address.lower()
try:
_print_aliase_info(address, ctx.hdlr.alias_info(address))
except VMMError as err:
if err.code is ACCOUNT_EXISTS:
w_err(0, ctx.plan_a_b % {'subcommand': 'userinfo',
'object': address})
- ctx.scmd = ctx.args[1] = 'userinfo'
+ ctx.args.scmd = 'userinfo'
+ ctx.args.details = None
user_info(ctx)
elif err.code is RELOCATED_EXISTS:
w_err(0, ctx.plan_a_b % {'subcommand': 'relocatedinfo',
'object': address})
- ctx.scmd = ctx.args[1] = 'relocatedinfo'
+ ctx.args.scmd = 'relocatedinfo'
relocated_info(ctx)
else:
raise
@@ -160,33 +93,25 @@
def aliasdomain_add(ctx):
"""create a new alias for an existing domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing alias domain name and destination '
- 'domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing destination domain name.'),
- ctx.scmd)
- ctx.hdlr.aliasdomain_add(ctx.args[2].lower(), ctx.args[3].lower())
+ ctx.hdlr.aliasdomain_add(ctx.args.fqdn.lower(),
+ ctx.args.destination.lower())
def aliasdomain_delete(ctx):
"""delete the specified alias domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing alias domain name.'), ctx.scmd)
- ctx.hdlr.aliasdomain_delete(ctx.args[2].lower())
+ ctx.hdlr.aliasdomain_delete(ctx.args.fqdn.lower())
def aliasdomain_info(ctx):
"""show the destination of the given alias domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing alias domain name.'), ctx.scmd)
+ fqdn = ctx.args.fqdn.lower()
try:
- _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(ctx.args[2].lower()))
+ _print_aliasdomain_info(ctx.hdlr.aliasdomain_info(fqdn))
except VMMError as err:
if err.code is ALIASDOMAIN_ISDOMAIN:
w_err(0, ctx.plan_a_b % {'subcommand': 'domaininfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'domaininfo'
+ 'object': fqdn})
+ ctx.args.scmd = 'domaininfo'
domain_info(ctx)
else:
raise
@@ -194,125 +119,79 @@
def aliasdomain_switch(ctx):
"""assign the given alias domain to an other domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing alias domain name and destination '
- 'domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing destination domain name.'),
- ctx.scmd)
- ctx.hdlr.aliasdomain_switch(ctx.args[2].lower(), ctx.args[3].lower())
+ ctx.hdlr.aliasdomain_switch(ctx.args.fqdn.lower(),
+ ctx.args.destination.lower())
def catchall_add(ctx):
"""create a new catchall alias e-mail address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain and destination.'),
- ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing destination address.'), ctx.scmd)
- ctx.hdlr.catchall_add(ctx.args[2].lower(), *ctx.args[3:])
+ ctx.hdlr.catchall_add(ctx.args.fqdn.lower(), *ctx.args.destination)
def catchall_delete(ctx):
"""delete the specified destination or all of the catchall destination"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.catchall_delete(ctx.args[2].lower())
- else:
- ctx.hdlr.catchall_delete(ctx.args[2].lower(), ctx.args[3:])
+ destination = ctx.args.destination if ctx.args.destination else None
+ ctx.hdlr.catchall_delete(ctx.args.fqdn.lower(), destination)
def catchall_info(ctx):
"""show the catchall destination(s) of the specified domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name.'), ctx.scmd)
- address = ctx.args[2].lower()
+ address = ctx.args.fqdn.lower()
_print_catchall_info(address, ctx.hdlr.catchall_info(address))
def config_get(ctx):
"""show the actual value of the configuration option"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _("Missing option name."), ctx.scmd)
-
noop = lambda option: option
opt_formater = {
'misc.dovecot_version': version_str,
'domain.quota_bytes': human_size,
}
- option = ctx.args[2].lower()
+ option = ctx.args.option.lower()
w_std('%s = %s' % (option, opt_formater.get(option,
noop)(ctx.cget(option))))
def config_set(ctx):
"""set a new value for the configuration option"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing option and new value.'), ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing new configuration value.'),
- ctx.scmd)
- ctx.hdlr.cfg_set(ctx.args[2].lower(), ctx.args[3])
+ ctx.hdlr.cfg_set(ctx.args.option.lower(), ctx.args.value)
def configure(ctx):
"""start interactive configuration mode"""
- if ctx.argc < 3:
- ctx.hdlr.configure()
- else:
- ctx.hdlr.configure(ctx.args[2].lower())
+ ctx.hdlr.configure(ctx.args.section)
def domain_add(ctx):
"""create a new domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.domain_add(ctx.args[2].lower())
- else:
- ctx.hdlr.domain_add(ctx.args[2].lower(), ctx.args[3])
+ fqdn = ctx.args.fqdn.lower()
+ transport = ctx.args.transport.lower if ctx.args.transport else None
+ ctx.hdlr.domain_add(fqdn, transport)
if ctx.cget('domain.auto_postmaster'):
- w_std(_('Creating account for postmaster@%s') % ctx.args[2].lower())
- ctx.scmd = 'useradd'
- ctx.args = [prog, ctx.scmd, 'postmaster@' + ctx.args[2].lower()]
- ctx.argc = 3
+ w_std(_('Creating account for postmaster@%s') % fqdn)
+ ctx.args.scmd = 'useradd'
+ ctx.args.address = 'postmaster@%s' % fqdn
+ ctx.args.password = None
user_add(ctx)
def domain_delete(ctx):
"""delete the given domain and all its alias domains"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.domain_delete(ctx.args[2].lower())
- elif ctx.args[3].lower() == 'force':
- ctx.hdlr.domain_delete(ctx.args[2].lower(), True)
- else:
- usage(INVALID_ARGUMENT, _("Invalid argument: '%s'") % ctx.args[3],
- ctx.scmd)
+ ctx.hdlr.domain_delete(ctx.args.fqdn.lower(), ctx.args.force)
def domain_info(ctx):
"""display information about the given domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name.'), ctx.scmd)
- if ctx.argc < 4:
- details = None
- else:
- details = ctx.args[3].lower()
- if details not in ('accounts', 'aliasdomains', 'aliases', 'full',
- 'relocated', 'catchall'):
- usage(INVALID_ARGUMENT, _("Invalid argument: '%s'") % details,
- ctx.scmd)
+ fqdn = ctx.args.fqdn.lower()
+ details = ctx.args.details
try:
- info = ctx.hdlr.domain_info(ctx.args[2].lower(), details)
+ info = ctx.hdlr.domain_info(fqdn, details)
except VMMError as err:
if err.code is DOMAIN_ALIAS_EXISTS:
w_err(0, ctx.plan_a_b % {'subcommand': 'aliasdomaininfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'aliasdomaininfo'
+ 'object': fqdn})
+ ctx.args.scmd = 'aliasdomaininfo'
aliasdomain_info(ctx)
else:
raise
@@ -349,151 +228,40 @@
def domain_quota(ctx):
"""update the quota limit of the specified domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name and storage value.'),
- ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing storage value.'), ctx.scmd)
- messages = 0
- force = None
- try:
- bytes_ = size_in_bytes(ctx.args[3])
- except (ValueError, TypeError):
- usage(INVALID_ARGUMENT, _("Invalid storage value: '%s'") %
- ctx.args[3], ctx.scmd)
- if ctx.argc < 5:
- pass
- elif ctx.argc < 6:
- try:
- messages = int(ctx.args[4])
- except ValueError:
- if ctx.args[4].lower() != 'force':
- usage(INVALID_ARGUMENT,
- _("Neither a valid number of messages nor the keyword "
- "'force': '%s'") % ctx.args[4], ctx.scmd)
- force = 'force'
- else:
- try:
- messages = int(ctx.args[4])
- except ValueError:
- usage(INVALID_ARGUMENT,
- _("Not a valid number of messages: '%s'") % ctx.args[4],
- ctx.scmd)
- if ctx.args[5].lower() != 'force':
- usage(INVALID_ARGUMENT, _("Invalid argument: '%s'") % ctx.args[5],
- ctx.scmd)
- force = 'force'
- ctx.hdlr.domain_quotalimit(ctx.args[2].lower(), bytes_, messages, force)
+ force = 'force' if ctx.args.force else None
+ ctx.hdlr.domain_quotalimit(ctx.args.fqdn.lower(), ctx.args.storage,
+ ctx.args.messages, force)
def domain_services(ctx):
"""allow all named service and block the uncredited."""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name.'), ctx.scmd)
- services = []
- force = False
- if ctx.argc is 3:
- pass
- elif ctx.argc is 4:
- arg = ctx.args[3].lower()
- if arg in SERVICES:
- services.append(arg)
- elif arg == 'force':
- force = True
- else:
- usage(INVALID_ARGUMENT, _("Invalid argument: '%s'") % arg,
- ctx.scmd)
- else:
- services.extend([service.lower() for service in ctx.args[3:-1]])
- arg = ctx.args[-1].lower()
- if arg == 'force':
- force = True
- else:
- services.append(arg)
- unknown = [service for service in services if service not in SERVICES]
- if unknown:
- usage(INVALID_ARGUMENT, _('Invalid service arguments: %s') %
- ' '.join(unknown), ctx.scmd)
- ctx.hdlr.domain_services(ctx.args[2].lower(), (None, 'force')[force],
- *services)
+ force = 'force' if ctx.args.force else None
+ services = ctx.args.services if ctx.args.services else []
+ ctx.hdlr.domain_services(ctx.args.fqdn.lower(), force, *services)
def domain_transport(ctx):
"""update the transport of the specified domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name and new transport.'),
- ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing new transport.'), ctx.scmd)
- if ctx.argc < 5:
- ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3])
- else:
- force = ctx.args[4].lower()
- if force != 'force':
- usage(INVALID_ARGUMENT, _("Invalid argument: '%s'") % force,
- ctx.scmd)
- ctx.hdlr.domain_transport(ctx.args[2].lower(), ctx.args[3], force)
+ force = 'force' if ctx.args.force else None
+ ctx.hdlr.domain_transport(ctx.args.fqdn.lower(),
+ ctx.args.transport.lower(), force)
def domain_note(ctx):
"""update the note of the given domain"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing domain name.'),
- ctx.scmd)
- elif ctx.argc < 4:
- note = None
- else:
- note = ' '.join(ctx.args[3:])
- ctx.hdlr.domain_note(ctx.args[2].lower(), note)
+ ctx.hdlr.domain_note(ctx.args.fqdn.lower(), ctx.args.note)
def get_user(ctx):
"""get the address of the user with the given UID"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing UID.'), ctx.scmd)
- _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args[2]), _('Account'))
-
-
-def help_(ctx):
- """print help messages."""
- if ctx.argc > 2:
- hlptpc = ctx.args[2].lower()
- if hlptpc in cmd_map:
- topic = hlptpc
- else:
- for scmd in cmd_map.values():
- if scmd.alias == hlptpc:
- topic = scmd.name
- break
- else:
- usage(INVALID_ARGUMENT, _("Unknown help topic: '%s'") %
- ctx.args[2], ctx.scmd)
- if topic != 'help':
- return cmd_map[topic].help_()
-
- old_ii = txt_wrpr.initial_indent
- old_si = txt_wrpr.subsequent_indent
- txt_wrpr.initial_indent = ' '
- # len(max(_overview.iterkeys(), key=len)) #Py25
- txt_wrpr.subsequent_indent = 20 * ' '
- order = sorted(list(cmd_map.keys()))
-
- w_std(_('List of available subcommands:') + '\n')
- for key in order:
- w_std('\n'.join(txt_wrpr.wrap('%-18s %s' % (key, cmd_map[key].descr))))
-
- txt_wrpr.initial_indent = old_ii
- txt_wrpr.subsequent_indent = old_si
- txt_wrpr.initial_indent = ''
+ _print_info(ctx, ctx.hdlr.user_by_uid(ctx.args.uid), _('Account'))
def list_domains(ctx):
"""list all domains / search domains by pattern"""
- matching = ctx.argc > 2
- if matching:
- gids, domains = ctx.hdlr.domain_list(ctx.args[2].lower())
- else:
- gids, domains = ctx.hdlr.domain_list()
+ matching = True if ctx.args.pattern else False
+ pattern = ctx.args.pattern.lower() if matching else None
+ gids, domains = ctx.hdlr.domain_list(pattern)
_print_domain_list(gids, domains, matching)
@@ -519,11 +287,9 @@
combining all three."""
if limit is None:
limit = TYPE_ACCOUNT | TYPE_ALIAS | TYPE_RELOCATED
- matching = ctx.argc > 2
- if matching:
- gids, addresses = ctx.hdlr.address_list(limit, ctx.args[2].lower())
- else:
- gids, addresses = ctx.hdlr.address_list(limit)
+ matching = True if ctx.args.pattern else False
+ pattern = ctx.args.pattern.lower() if matching else None
+ gids, addresses = ctx.hdlr.address_list(limit, pattern)
_print_address_list(limit, gids, addresses, matching)
@@ -544,26 +310,17 @@
def relocated_add(ctx):
"""create a new record for a relocated user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS,
- _('Missing relocated address and destination.'), ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing destination address.'), ctx.scmd)
- ctx.hdlr.relocated_add(ctx.args[2].lower(), ctx.args[3])
+ ctx.hdlr.relocated_add(ctx.args.address.lower(), ctx.args.newaddress)
def relocated_delete(ctx):
"""delete the record of the relocated user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing relocated address.'), ctx.scmd)
- ctx.hdlr.relocated_delete(ctx.args[2].lower())
+ ctx.hdlr.relocated_delete(ctx.args.address.lower())
def relocated_info(ctx):
"""print information about a relocated user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing relocated address.'), ctx.scmd)
- relocated = ctx.args[2].lower()
+ relocated = ctx.args.address.lower()
try:
_print_relocated_info(addr=relocated,
dest=ctx.hdlr.relocated_info(relocated))
@@ -571,12 +328,13 @@
if err.code is ACCOUNT_EXISTS:
w_err(0, ctx.plan_a_b % {'subcommand': 'userinfo',
'object': relocated})
- ctx.scmd = ctx.args[1] = 'userinfoi'
+ ctx.args.scmd = 'userinfo'
+ ctx.args.details = None
user_info(ctx)
elif err.code is ALIAS_EXISTS:
w_err(0, ctx.plan_a_b % {'subcommand': 'aliasinfo',
'object': relocated})
- ctx.scmd = ctx.args[1] = 'aliasinfo'
+ ctx.args.scmd = 'aliasinfo'
alias_info(ctx)
else:
raise
@@ -584,58 +342,36 @@
def user_add(ctx):
"""create a new e-mail user with the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address.'), ctx.scmd)
- elif ctx.argc < 4:
- password = None
- else:
- password = ctx.args[3]
- gen_pass = ctx.hdlr.user_add(ctx.args[2].lower(), password)
- if ctx.argc < 4 and gen_pass:
+ gen_pass = ctx.hdlr.user_add(ctx.args.address.lower(), ctx.args.password)
+ if not ctx.args.password and gen_pass:
w_std(_("Generated password: %s") % gen_pass)
def user_delete(ctx):
"""delete the specified user"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address.'), ctx.scmd)
- elif ctx.argc < 4:
- ctx.hdlr.user_delete(ctx.args[2].lower())
- elif ctx.args[3].lower() == 'force':
- ctx.hdlr.user_delete(ctx.args[2].lower(), True)
- else:
- usage(INVALID_ARGUMENT, _("Invalid argument: '%s'") % ctx.args[3],
- ctx.scmd)
+ ctx.hdlr.user_delete(ctx.args.address.lower(), ctx.args.force)
def user_info(ctx):
"""display information about the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address.'), ctx.scmd)
- if ctx.argc < 4:
- details = None
- else:
- details = ctx.args[3].lower()
- if details not in ('aliases', 'du', 'full'):
- usage(INVALID_ARGUMENT, _("Invalid argument: '%s'") % details,
- ctx.scmd)
+ address = ctx.args.address.lower()
try:
- info = ctx.hdlr.user_info(ctx.args[2].lower(), details)
+ info = ctx.hdlr.user_info(address, ctx.args.details)
except VMMError as err:
if err.code is ALIAS_EXISTS:
w_err(0, ctx.plan_a_b % {'subcommand': 'aliasinfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'aliasinfo'
+ 'object': address})
+ ctx.args.scmd = 'aliasinfo'
alias_info(ctx)
elif err.code is RELOCATED_EXISTS:
w_err(0, ctx.plan_a_b % {'subcommand': 'relocatedinfo',
- 'object': ctx.args[2].lower()})
- ctx.scmd = ctx.args[1] = 'relocatedinfo'
+ 'object': address})
+ ctx.args.scmd = 'relocatedinfo'
relocated_info(ctx)
else:
raise
else:
- if details in (None, 'du'):
+ if ctx.args.details in (None, 'du'):
info['quota storage'] = _format_quota_usage(info['ql_bytes'],
info['uq_bytes'], True, info['ql_domaindefault'])
info['quota messages'] = \
@@ -656,265 +392,630 @@
def user_name(ctx):
"""set or update the real name for an address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _("Missing e-mail address and user's name."),
- ctx.scmd)
- elif ctx.argc < 4:
- name = None
- else:
- name = ctx.args[3]
- ctx.hdlr.user_name(ctx.args[2].lower(), name)
+ ctx.hdlr.user_name(ctx.args.address.lower(), ctx.args.name)
def user_password(ctx):
"""update the password for the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address.'), ctx.scmd)
- elif ctx.argc < 4:
- password = None
- else:
- password = ctx.args[3]
- ctx.hdlr.user_password(ctx.args[2].lower(), password)
+ ctx.hdlr.user_password(ctx.args.address.lower(), ctx.args.password)
def user_note(ctx):
"""update the note of the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address.'),
- ctx.scmd)
- elif ctx.argc < 4:
- note = None
- else:
- note = ' '.join(ctx.args[3:])
- ctx.hdlr.user_note(ctx.args[2].lower(), note)
+ ctx.hdlr.user_note(ctx.args.address.lower(), ctx.args.note)
def user_quota(ctx):
"""update the quota limit for the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address and storage value.'),
- ctx.scmd)
- elif ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing storage value.'), ctx.scmd)
- if ctx.args[3] != 'domain':
- try:
- bytes_ = size_in_bytes(ctx.args[3])
- except (ValueError, TypeError):
- usage(INVALID_ARGUMENT, _("Invalid storage value: '%s'") %
- ctx.args[3], ctx.scmd)
- else:
- bytes_ = ctx.args[3]
- if ctx.argc < 5:
- messages = 0
- else:
- try:
- messages = int(ctx.args[4])
- except ValueError:
- usage(INVALID_ARGUMENT,
- _("Not a valid number of messages: '%s'") % ctx.args[4],
- ctx.scmd)
- ctx.hdlr.user_quotalimit(ctx.args[2].lower(), bytes_, messages)
+ ctx.hdlr.user_quotalimit(ctx.args.address.lower(), ctx.args.storage,
+ ctx.args.messages)
def user_services(ctx):
"""allow all named service and block the uncredited."""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address.'), ctx.scmd)
- services = []
- if ctx.argc >= 4:
- services.extend([service.lower() for service in ctx.args[3:]])
- unknown = [service for service in services if service not in SERVICES]
- if unknown and ctx.args[3] != 'domain':
- usage(INVALID_ARGUMENT, _('Invalid service arguments: %s') %
- ' '.join(unknown), ctx.scmd)
- ctx.hdlr.user_services(ctx.args[2].lower(), *services)
+ if 'domain' in ctx.args.services:
+ services = ['domain']
+ else:
+ services = ctx.args.services
+ ctx.hdlr.user_services(ctx.args.address.lower(), *services)
def user_transport(ctx):
"""update the transport of the given address"""
- if ctx.argc < 3:
- usage(EX_MISSING_ARGS, _('Missing e-mail address and transport.'),
- ctx.scmd)
- if ctx.argc < 4:
- usage(EX_MISSING_ARGS, _('Missing transport.'), ctx.scmd)
- ctx.hdlr.user_transport(ctx.args[2].lower(), ctx.args[3])
-
-
-def usage(errno, errmsg, subcommand=None):
- """print usage message for the given command or all commands.
- When errno > 0, sys,exit(errno) will interrupt the program.
- """
- if subcommand and subcommand in cmd_map:
- w_err(errno, _("Error: %s") % errmsg,
- _("usage: ") + cmd_map[subcommand].usage)
-
- # TP: Please adjust translated words like the original text.
- # (It's a table header.) Extract from usage text:
- # usage: vmm subcommand arguments
- # short long
- # subcommand arguments
- #
- # da domainadd fqdn [transport]
- # dd domaindelete fqdn [force]
- u_head = _("""usage: %s subcommand arguments
- short long
- subcommand arguments\n""") % prog
- order = sorted(list(cmd_map.keys()))
- w_err(0, u_head)
- for key in order:
- scmd = cmd_map[key]
- w_err(0, ' %-5s %-19s %s' % (scmd.alias, scmd.name, scmd.args))
- w_err(errno, '', _("Error: %s") % errmsg)
-
-
-def version(ctx_unused):
- """Write version and copyright information to stdout."""
- w_std('%s, %s %s (%s %s)\nPython %s %s %s\n\n%s\n%s %s' % (prog,
- # TP: The words 'from', 'version' and 'on' are used in
- # the version information, e.g.:
- # vmm, version 0.5.2 (from 09/09/09)
- # Python 2.5.4 on FreeBSD
- _('version'), __version__, _('from'),
- strftime(locale.nl_langinfo(locale.D_FMT),
- strptime(__date__, '%Y-%m-%d')),
- os.sys.version.split()[0], _('on'), os.uname()[0],
- __copyright__, prog,
- _('is free software and comes with ABSOLUTELY NO WARRANTY.')))
+ ctx.hdlr.user_transport(ctx.args.address.lower(), ctx.args.transport)
-def update_cmd_map():
- """Update the cmd_map, after gettext's _ was installed."""
- cmd = Command
- cmd_map.update({
- # Account commands
- 'getuser': cmd('getuser', 'gu', get_user, 'uid',
- _('get the address of the user with the given UID')),
- 'useradd': cmd('useradd', 'ua', user_add, 'address [password]',
- _('create a new e-mail user with the given address')),
- 'userdelete': cmd('userdelete', 'ud', user_delete, 'address [force]',
- _('delete the specified user')),
- 'userinfo': cmd('userinfo', 'ui', user_info, 'address [details]',
- _('display information about the given address')),
- 'username': cmd('username', 'un', user_name, 'address [name]',
- _('set, update or delete the real name for an address')),
- 'userpassword': cmd('userpassword', 'up', user_password,
- 'address [password]',
- _('update the password for the given address')),
- 'userquota': cmd('userquota', 'uq', user_quota,
- 'address storage [messages] | address domain',
- _('update the quota limit for the given address')),
- 'userservices': cmd('userservices', 'us', user_services,
- 'address [service ...] | address domain',
- _('enables the specified services and disables all '
- 'not specified services')),
- 'usertransport': cmd('usertransport', 'ut', user_transport,
- 'address transport | address domain',
- _('update the transport of the given address')),
- 'usernote': cmd('usernote', 'uo', user_note, 'address [note]',
- _('set, update or delete the note of the given address')),
- # Alias commands
- 'aliasadd': cmd('aliasadd', 'aa', alias_add, 'address destination ...',
- _('create a new alias e-mail address with one or more '
- 'destinations')),
- 'aliasdelete': cmd('aliasdelete', 'ad', alias_delete,
- 'address [destination ...]',
- _('delete the specified alias e-mail address or one '
- 'of its destinations')),
- 'aliasinfo': cmd('aliasinfo', 'ai', alias_info, 'address',
- _('show the destination(s) of the specified alias')),
- # AliasDomain commands
- 'aliasdomainadd': cmd('aliasdomainadd', 'ada', aliasdomain_add,
- 'fqdn destination',
- _('create a new alias for an existing domain')),
- 'aliasdomaindelete': cmd('aliasdomaindelete', 'add', aliasdomain_delete,
- 'fqdn', _('delete the specified alias domain')),
- 'aliasdomaininfo': cmd('aliasdomaininfo', 'adi', aliasdomain_info, 'fqdn',
- _('show the destination of the given alias domain')),
- 'aliasdomainswitch': cmd('aliasdomainswitch', 'ads', aliasdomain_switch,
- 'fqdn destination', _('assign the given alias '
- 'domain to an other domain')),
- # CatchallAlias commands
- 'catchalladd': cmd('catchalladd', 'caa', catchall_add,
- 'fqdn destination ...',
- _('add one or more catch-all destinations for a '
- 'domain')),
- 'catchalldelete': cmd('catchalldelete', 'cad', catchall_delete,
- 'fqdn [destination ...]',
- _('delete the specified catch-all destination or all '
- 'of a domain\'s destinations')),
- 'catchallinfo': cmd('catchallinfo', 'cai', catchall_info, 'fqdn',
- _('show the catch-all destination(s) of the '
- 'specified domain')),
- # Domain commands
- 'domainadd': cmd('domainadd', 'da', domain_add, 'fqdn [transport]',
- _('create a new domain')),
- 'domaindelete': cmd('domaindelete', 'dd', domain_delete, 'fqdn [force]',
- _('delete the given domain and all its alias domains')),
- 'domaininfo': cmd('domaininfo', 'di', domain_info, 'fqdn [details]',
- _('display information about the given domain')),
- 'domainquota': cmd('domainquota', 'dq', domain_quota,
- 'fqdn storage [messages] [force]',
- _('update the quota limit of the specified domain')),
- 'domainservices': cmd('domainservices', 'ds', domain_services,
- 'fqdn [service ...] [force]',
- _('enables the specified services and disables all '
- 'not specified services of the given domain')),
- 'domaintransport': cmd('domaintransport', 'dt', domain_transport,
- 'fqdn transport [force]',
- _('update the transport of the specified domain')),
- 'domainnote': cmd('domainnote', 'do', domain_note, 'fqdn [note]',
- _('set, update or delete the note of the given domain')),
- # List commands
- 'listdomains': cmd('listdomains', 'ld', list_domains, '[pattern]',
- _('list all domains or search for domains by pattern')),
- 'listaddresses': cmd('listaddresses', 'll', list_addresses, '[pattern]',
- _('list all addresses or search for addresses by '
- 'pattern')),
- 'listusers': cmd('listusers', 'lu', list_users, '[pattern]',
- _('list all user accounts or search for accounts by '
- 'pattern')),
- 'listaliases': cmd('listaliases', 'la', list_aliases, '[pattern]',
- _('list all aliases or search for aliases by pattern')),
- 'listrelocated': cmd('listrelocated', 'lr', list_relocated, '[pattern]',
- _('list all relocated users or search for relocated '
- 'users by pattern')),
- # Relocated commands
- 'relocatedadd': cmd('relocatedadd', 'ra', relocated_add,
- 'address newaddress',
- _('create a new record for a relocated user')),
- 'relocateddelete': cmd('relocateddelete', 'rd', relocated_delete,
- 'address',
- _('delete the record of the relocated user')),
- 'relocatedinfo': cmd('relocatedinfo', 'ri', relocated_info, 'address',
- _('print information about a relocated user')),
- # cli commands
- 'configget': cmd('configget', 'cg', config_get, 'option',
- _('show the actual value of the configuration option')),
- 'configset': cmd('configset', 'cs', config_set, 'option value',
- _('set a new value for the configuration option')),
- 'configure': cmd('configure', 'cf', configure, '[section]',
- _('start interactive configuration mode')),
- 'listpwschemes': cmd('listpwschemes', 'lp', list_pwschemes, '',
- _('lists all usable password schemes and password '
- 'encoding suffixes')),
- 'help': cmd('help', 'h', help_, '[subcommand]',
- _('show a help overview or help for the given subcommand')),
- 'version': cmd('version', 'v', version, '',
- _('show version and copyright information')),
- })
+def setup_parser():
+ """Create the argument parser, add all the subcommands and return it."""
+ class ArgParser(ArgumentParser):
+ """This class fixes the 'width detection'."""
+ def _get_formatter(self):
+ return self.formatter_class(prog=self.prog, width=WS_ROWS,
+ max_help_position=26)
+
+ class VersionAction(Action):
+ """Show version and copyright information."""
+ def __call__(self, parser, namespace, values, option_string=None):
+ """implements the Action API."""
+ vers_info = _('{program}, version {version} (from {rel_date})\n'
+ 'Python {py_vers} on {sysname}'.format(
+ program=parser.prog, version=__version__,
+ rel_date=strftime(
+ locale.nl_langinfo(locale.D_FMT),
+ strptime(__date__, '%Y-%m-%d')),
+ py_vers=platform.python_version(),
+ sysname=platform.system()))
+ copy_info = _('{copyright}\n{program} is free software and comes '
+ 'with ABSOLUTELY NO WARRANTY.'.format(
+ copyright=__copyright__, program=parser.prog))
+ parser.exit(message='\n\n'.join((vers_info, copy_info)) + '\n')
+
+ def quota_storage(string):
+ if string == 'domain':
+ return string
+ try:
+ storage = size_in_bytes(string)
+ except (TypeError, ValueError) as error:
+ raise ArgumentTypeError(str(error))
+ return storage
+
+ old_rw = txt_wrpr.replace_whitespace
+ txt_wrpr.replace_whitespace = False
+ fill = lambda t: '\n'.join(txt_wrpr.fill(l) for l in t.splitlines(True))
+ mklst = lambda iterable: '\n\t - ' + '\n\t - '.join(iterable)
+
+ description = _('%(prog)s - command line tool to manage email '
+ 'domains/accounts/aliases/...')
+ epilog = _('use "%(prog)s <subcommand> -h" for information about the '
+ 'given subcommand')
+ parser = ArgParser(description=description, epilog=epilog)
+ parser.add_argument('-v', '--version', action=VersionAction, nargs=0,
+ help=_("show %(prog)s's version and copyright "
+ "information and exit"))
+ subparsers = parser.add_subparsers(metavar=_('<subcommand>'),
+ title=_('list of available subcommands'))
+ a = subparsers.add_parser
+
+ ###
+ # general subcommands
+ ###
+ cg = a('configget', aliases=('cg',),
+ help=_('show the actual value of the configuration option'),
+ epilog=_("This subcommand is used to display the actual value of "
+ "the given configuration option."))
+ cg.add_argument('option', help=_('the name of a configuration option'))
+ cg.set_defaults(func=config_get, scmd='configget')
+
+ cs = a('configset', aliases=('cs',),
+ help=_('set a new value for the configuration option'),
+ epilog=fill(_("Use this subcommand to set or update a single "
+ "configuration option's value. option is the configuration "
+ "option, value is the option's new value.\n\nNote: This "
+ "subcommand will create a new vmm.cfg without any comments. "
+ "Your current configuration file will be backed as "
+ "vmm.cfg.bak.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ cs.add_argument('option', help=_('the name of a configuration option'))
+ cs.add_argument('value', help=_("the option's new value"))
+ cs.set_defaults(func=config_set, scmd='configset')
+
+ sections = ('account', 'bin', 'database', 'domain', 'mailbox', 'misc')
+ cf = a('configure', aliases=('cf',),
+ help=_('start interactive configuration mode'),
+ epilog=fill(_("Starts the interactive configuration for all "
+ "configuration sections.\n\nIn this process the currently set "
+ "value of each option will be displayed in square brackets. "
+ "If no value is configured, the default value of each option "
+ "will be displayed in square brackets. Press the return key, "
+ "to accept the displayed value.\n\n"
+ "If the optional argument section is given, only the "
+ "configuration options from the given section will be "
+ "displayed and will be configurable. The following sections "
+ "are available:\n") + mklst(sections)),
+ formatter_class=RawDescriptionHelpFormatter)
+ cf.add_argument('-s', choices=sections, metavar='SECTION', dest='section',
+ help=_("configure only options of the given section"))
+ cf.set_defaults(func=configure, scmd='configure')
+
+ gu = a('getuser', aliases=('gu',),
+ help=_('get the address of the user with the given UID'),
+ epilog=_("If only the uid is available, for example from process "
+ "list, the subcommand getuser will show the user's "
+ "address."))
+ gu.add_argument('uid', type=int, help=_("a user's unique identifier"))
+ gu.set_defaults(func=get_user, scmd='getuser')
+
+ ll = a('listaddresses', aliases=('ll',),
+ help=_('list all addresses or search for addresses by pattern'),
+ epilog=fill(_("This command lists all defined addresses. "
+ "Addresses belonging to alias-domains are prefixed with a '-', "
+ "addresses of regular domains with a '+'. Additionally, the "
+ "letters 'u', 'a', and 'r' indicate the type of each address: "
+ "user, alias and relocated respectively. The output can be "
+ "limited with an optional pattern.\n\nTo perform a wild card "
+ "search, the % character can be used at the start and/or the "
+ "end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ll.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ ll.set_defaults(func=list_addresses, scmd='listaddresses')
+
+ la = a('listaliases', aliases=('la',),
+ help=_('list all aliases or search for aliases by pattern'),
+ epilog=fill(_("This command lists all defined aliases. Aliases "
+ "belonging to alias-domains are prefixed with a '-', addresses "
+ "of regular domains with a '+'. The output can be limited "
+ "with an optional pattern.\n\nTo perform a wild card search, "
+ "the % character can be used at the start and/or the end of "
+ "the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ la.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ la.set_defaults(func=list_aliases, scmd='listaliases')
+
+ ld = a('listdomains', aliases=('ld',),
+ help=_('list all domains or search for domains by pattern'),
+ epilog=fill(_("This subcommand lists all available domains. All "
+ "domain names will be prefixed either with `[+]', if the "
+ "domain is a primary domain, or with `[-]', if it is an alias "
+ "domain name. The output can be limited with an optional "
+ "pattern.\n\nTo perform a wild card search, the % character "
+ "can be used at the start and/or the end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ld.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ ld.set_defaults(func=list_domains, scmd='listdomains')
+
+ lr = a('listrelocated', aliases=('lr',),
+ help=_('list all relocated users or search for relocated users by '
+ 'pattern'),
+ epilog=fill(_("This command lists all defined relocated addresses. "
+ "Relocated entries belonging to alias-domains are prefixed "
+ "with a '-', addresses of regular domains with a '+'. The "
+ "output can be limited with an optional pattern.\n\nTo "
+ "perform a wild card search, the % character can be used at "
+ "the start and/or the end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ lr.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ lr.set_defaults(func=list_relocated, scmd='listrelocated')
+
+ lu = a('listusers', aliases=('lu',),
+ help=_('list all user accounts or search for accounts by pattern'),
+ epilog=fill(_("This command lists all user accounts. User accounts "
+ "belonging to alias-domains are prefixed with a '-', "
+ "addresses of regular domains with a '+'. The output can be "
+ "limited with an optional pattern.\n\nTo perform a wild card "
+ "search, the % character can be used at the start and/or the "
+ "end of the pattern.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ lu.add_argument('-p', help=_("the pattern to search for"),
+ metavar='PATTERN', dest='pattern')
+ lu.set_defaults(func=list_users, scmd='listusers')
+
+ lp = a('listpwschemes', aliases=('lp',),
+ help=_('lists all usable password schemes and password encoding '
+ 'suffixes'),
+ epilog=fill(_("This subcommand lists all password schemes which "
+ "could be used in the vmm.cfg as value of the "
+ "misc.password_scheme option. The output varies, depending "
+ "on the used Dovecot version and the system's libc.\nWhen "
+ "your Dovecot installation isn't too old, you will see "
+ "additionally a few usable encoding suffixes. One of them can "
+ "be appended to the password scheme.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ lp.set_defaults(func=list_pwschemes, scmd='listpwschemes')
+
+ ###
+ # domain subcommands
+ ###
+ da = a('domainadd', aliases=('da',), help=_('create a new domain'),
+ epilog=fill(_("Adds the new domain into the database and creates "
+ "the domain directory.\n\nIf the optional argument transport "
+ "is given, it will override the default transport "
+ "(domain.transport) from vmm.cfg. The specified transport "
+ "will be the default transport for all new accounts in this "
+ "domain.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ da.add_argument('fqdn', help=_('a fully qualified domain name'))
+ da.add_argument('-t', metavar='TRANSPORT', dest='transport',
+ help=_('a Postfix transport (transport: or '
+ 'transport:nexthop)'))
+ da.set_defaults(func=domain_add, scmd='domainadd')
+
+ details = ('accounts', 'aliasdomains', 'aliases', 'catchall', 'relocated',
+ 'full')
+ di = a('domaininfo', aliases=('di',),
+ help=_('display information about the given domain'),
+ epilog=fill(_("This subcommand shows some information about the "
+ "given domain.\n\nFor a more detailed information about the "
+ "domain the optional argument details can be specified. A "
+ "possible details value can be one of the following six "
+ "keywords:\n") + mklst(details)),
+ formatter_class=RawDescriptionHelpFormatter)
+ di.add_argument('fqdn', help=_('a fully qualified domain name'))
+ di.add_argument('-d', choices=details, dest='details', metavar='DETAILS',
+ help=_('additionally details to display'))
+ di.set_defaults(func=domain_info, scmd='domaininfo')
+
+ do = a('domainnote', aliases=('do',),
+ help=_('set, update or delete the note of the given domain'),
+ epilog=_('With this subcommand, it is possible to attach a note to '
+ 'the specified domain. Without an argument, an existing '
+ 'note is removed.'))
+ do.add_argument('fqdn', help=_('a fully qualified domain name'))
+ do.add_argument('-n', metavar='NOTE', dest='note',
+ help=_('the note that should be set'))
+ do.set_defaults(func=domain_note, scmd='domainnote')
+
+ dq = a('domainquota', aliases=('dq',),
+ help=_('update the quota limit of the specified domain'),
+ epilog=fill(_("This subcommand is used to configure a new quota "
+ "limit for the accounts of the domain - not for the domain "
+ "itself.\n\nThe default quota limit for accounts is defined "
+ "in the vmm.cfg (domain.quota_bytes and "
+ "domain.quota_messages).\n\nThe new quota limit will affect "
+ "only those accounts for which the limit has not been "
+ "overridden. If you want to restore the default to all "
+ "accounts, you may pass the optional argument --force. When "
+ "the argument messages was omitted the default number of "
+ "messages 0 (zero) will be applied.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ dq.add_argument('fqdn', help=_('a fully qualified domain name'))
+ dq.add_argument('storage', type=quota_storage,
+ help=_('quota limit in {kilo,mega,giga}bytes e.g. 2G '
+ 'or 2048M',))
+ dq.add_argument('-m', default=0, type=int, metavar='MESSAGES',
+ dest='messages',
+ help=_('quota limit in number of messages (default: 0)'))
+ dq.add_argument('--force', action='store_true',
+ help=_('enforce the limit for all accounts'))
+ dq.set_defaults(func=domain_quota, scmd='domainquota')
+
+ ds = a('domainservices', aliases=('ds',),
+ help=_('enables the specified services and disables all not '
+ 'specified services of the given domain'),
+ epilog=fill(_("To define which services could be used by the users "
+ "of the domain — with the given fqdn — use this "
+ "subcommand.\n\nEach specified service will be enabled/"
+ "usable. All other services will be deactivated/unusable. "
+ "Possible service names are: imap, pop3, sieve and smtp.\nThe "
+ "new service set will affect only those accounts for which "
+ "the set has not been overridden. If you want to restore the "
+ "default to all accounts, you may pass --force.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ds.add_argument('fqdn', help=_('a fully qualified domain name'))
+ ds.add_argument('-s', choices=SERVICES,
+ help=_('services which should be usable'),
+ metavar='SERVICE', nargs='+', dest='services')
+ ds.add_argument('--force', action='store_true',
+ help=_('enforce the service set for all accounts'))
+ ds.set_defaults(func=domain_services, scmd='domainservices')
+
+ dt = a('domaintransport', aliases=('dt',),
+ help=_('update the transport of the specified domain'),
+ epilog=fill(_("A new transport for the indicated domain can be set "
+ "with this subcommand.\n\nThe new transport will affect only "
+ "those accounts for which the transport has not been "
+ "overridden. If you want to restore the default to all "
+ "accounts, you may pass --force.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ dt.add_argument('fqdn', help=_('a fully qualified domain name'))
+ dt.add_argument('transport', help=_('a Postfix transport (transport: or '
+ 'transport:nexthop)'))
+ dt.add_argument('--force', action='store_true',
+ help=_('enforce the transport for all accounts'))
+ dt.set_defaults(func=domain_transport, scmd='domaintransport')
+
+ dd = a('domaindelete', aliases=('dd',),
+ help=_('delete the given domain and all its alias domains'),
+ epilog=fill(_("This subcommand deletes the domain specified by "
+ "fqdn.\n\nIf there are accounts, aliases and/or relocated "
+ "users assigned to the given domain, vmm will abort the "
+ "requested operation and show an error message. If you know, "
+ "what you are doing, you can specify the optional argument "
+ "--force.\n\nIf you really always know what you are doing, "
+ "edit your vmm.cfg and set the option domain.force_deletion "
+ "to true.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ dd.add_argument('fqdn', help=_('a fully qualified domain name'))
+ dd.add_argument('--force', action='store_true',
+ help=_('also delete all accounts, aliases and/or '
+ 'relocated users'))
+ dd.set_defaults(func=domain_delete, scmd='domaindelete')
+
+ ###
+ # alias domain subcommands
+ ###
+ ada = a('aliasdomainadd', aliases=('ada',),
+ help=_('create a new alias for an existing domain'),
+ epilog=_('This subcommand adds the new alias domain (fqdn) to '
+ 'the destination domain that should be aliased.'))
+ ada.add_argument('fqdn', help=_('a fully qualified domain name'))
+ ada.add_argument('destination',
+ help=_('the fqdn of the destination domain'))
+ ada.set_defaults(func=aliasdomain_add, scmd='aliasdomainadd')
+
+ adi = a('aliasdomaininfo', aliases=('adi',),
+ help=_('show the destination of the given alias domain'),
+ epilog=_('This subcommand shows to which domain the alias domain '
+ 'fqdn is assigned to.'))
+ adi.add_argument('fqdn', help=_('a fully qualified domain name'))
+ adi.set_defaults(func=aliasdomain_info, scmd='aliasdomaininfo')
+
+ ads = a('aliasdomainswitch', aliases=('ads',),
+ help=_('assign the given alias domain to an other domain'),
+ epilog=_('If the destination of the existing alias domain fqdn '
+ 'should be switched to another destination use this '
+ 'subcommand.'))
+ ads.add_argument('fqdn', help=_('a fully qualified domain name'))
+ ads.add_argument('destination',
+ help=_('the fqdn of the destination domain'))
+ ads.set_defaults(func=aliasdomain_switch, scmd='aliasdomainswitch')
+
+ add = a('aliasdomaindelete', aliases=('add',),
+ help=_('delete the specified alias domain'),
+ epilog=_('Use this subcommand if the alias domain fqdn should be '
+ 'removed.'))
+ add.add_argument('fqdn', help=_('a fully qualified domain name'))
+ add.set_defaults(func=aliasdomain_delete, scmd='aliasdomaindelete')
+
+ ###
+ # account subcommands
+ ###
+ ua = a('useradd', aliases=('ua',),
+ help=_('create a new e-mail user with the given address'),
+ epilog=fill(_('Use this subcommand to create a new e-mail account '
+ 'for the given address.\n\nIf the password is not provided, '
+ 'vmm will prompt for it interactively. When no password is '
+ 'provided and account.random_password is set to true, vmm '
+ 'will generate a random password and print it to stdout '
+ 'after the account has been created.')),
+ formatter_class=RawDescriptionHelpFormatter)
+ ua.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ua.add_argument('-p', metavar='PASSWORD', dest='password',
+ help=_("the new user's password"))
+ ua.set_defaults(func=user_add, scmd='useradd')
+
+ details = ('aliases', 'du', 'full')
+ ui = a('userinfo', aliases=('ui',),
+ help=_('display information about the given address'),
+ epilog=fill(_('This subcommand displays some information about '
+ 'the account specified by the given address.\n\nIf the '
+ 'optional argument details is given some more information '
+ 'will be displayed.\nPossible values for details are:\n') +
+ mklst(details)),
+ formatter_class=RawDescriptionHelpFormatter)
+ ui.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ui.add_argument('-d', choices=details, metavar='DETAILS', dest='details',
+ help=_('additionally details to display'))
+ ui.set_defaults(func=user_info, scmd='userinfo')
+
+ un = a('username', aliases=('un',),
+ help=_('set, update or delete the real name for an address'),
+ epilog=fill(_("The user's real name can be set/updated with this "
+ "subcommand.\n\nIf no name is given, the value stored for the "
+ "account is erased.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ un.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ un.add_argument('-n', help=_("a user's real name"), metavar='NAME',
+ dest='name')
+ un.set_defaults(func=user_name, scmd='username')
+
+ uo = a('usernote', aliases=('uo',),
+ help=_('set, update or delete the note of the given address'),
+ epilog=_('With this subcommand, it is possible to attach a note to '
+ 'the specified account. Without the note argument, an '
+ 'existing note is removed.'))
+ uo.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ uo.add_argument('-n', metavar='NOTE', dest='note',
+ help=_('the note that should be set'))
+ uo.set_defaults(func=user_note, scmd='usernote')
+
+ up = a('userpassword', aliases=('up',),
+ help=_('update the password for the given address'),
+ epilog=fill(_("The password of an account can be updated with this "
+ "subcommand.\n\nIf no password was provided, vmm will prompt "
+ "for it interactively.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ up.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ up.add_argument('-p', metavar='PASSWORD', dest='password',
+ help=_("the user's new password"))
+ up.set_defaults(func=user_password, scmd='userpassword')
+
+ uq = a('userquota', aliases=('uq',),
+ help=_('update the quota limit for the given address'),
+ epilog=fill(_("This subcommand is used to set a new quota limit "
+ "for the given account.\n\nWhen the argument messages was "
+ "omitted the default number of messages 0 (zero) will be "
+ "applied.\n\nInstead of a storage limit pass the keyword "
+ "'domain' to remove the account-specific override, causing "
+ "the domain's value to be in effect.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ uq.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ uq.add_argument('storage', type=quota_storage,
+ help=_('quota limit in {kilo,mega,giga}bytes e.g. 2G '
+ 'or 2048M'))
+ uq.add_argument('-m', default=0, type=int, metavar='MESSAGES',
+ dest='messages',
+ help=_('quota limit in number of messages (default: 0)'))
+ uq.set_defaults(func=user_quota, scmd='userquota')
+
+ us = a('userservices', aliases=('us',),
+ help=_('enable the specified services and disables all not '
+ 'specified services'),
+ epilog=fill(_("To grant a user access to the specified service(s), "
+ "use this command.\n\nAll omitted services will be "
+ "deactivated/unusable for the user with the given "
+ "address.\n\nInstead of any service pass the keyword "
+ "'domain' to remove the account-specific override, causing "
+ "the domain's value to be in effect.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ us.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ us.add_argument('-s', choices=SERVICES + ('domain',),
+ help=_('services which should be usable'),
+ metavar='SERVICE', nargs='+', dest='services')
+ us.set_defaults(func=user_services, scmd='userservices')
+
+ ut = a('usertransport', aliases=('ut',),
+ help=_('update the transport of the given address'),
+ epilog=fill(_("A different transport for an account can be "
+ "specified with this subcommand.\n\nInstead of a transport "
+ "pass the keyword 'domain' to remove the account-specific "
+ "override, causing the domain's value to be in effect.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ut.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ut.add_argument('transport', help=_('a Postfix transport (transport: or '
+ 'transport:nexthop)'))
+ ut.set_defaults(func=user_transport, scmd='usertransport')
+
+ ud = a('userdelete', aliases=('ud',),
+ help=_('delete the specified user'),
+ epilog=fill(_('Use this subcommand to delete the account with the '
+ 'given address.\n\nIf there are one or more aliases with an '
+ 'identical destination address, vmm will abort the requested '
+ 'operation and show an error message. To prevent this, '
+ 'give the optional argument --force.')),
+ formatter_class=RawDescriptionHelpFormatter)
+ ud.add_argument('address',
+ help=_("an account's e-mail address (local-part@fqdn)"))
+ ud.add_argument('--force', action='store_true',
+ help=_('also delete assigned alias addresses'))
+ ud.set_defaults(func=user_delete, scmd='userdelete')
+
+ ###
+ # alias subcommands
+ ###
+ aa = a('aliasadd', aliases=('aa',),
+ help=_('create a new alias e-mail address with one or more '
+ 'destinations'),
+ epilog=fill(_("This subcommand is used to create a new alias "
+ "address with one or more destination addresses.\n\nWithin "
+ "the destination address, the placeholders %n, %d, and %= "
+ "will be replaced by the local part, the domain, or the "
+ "email address with '@' replaced by '=' respectively. In "
+ "combination with alias domains, this enables "
+ "domain-specific destinations.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ aa.add_argument('address',
+ help=_("an alias' e-mail address (local-part@fqdn)"))
+ aa.add_argument('destination', nargs='+',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ aa.set_defaults(func=alias_add, scmd='aliasadd')
+
+ ai = a('aliasinfo', aliases=('ai',),
+ help=_('show the destination(s) of the specified alias'),
+ epilog=_('Information about the alias with the given address can '
+ 'be displayed with this subcommand.'))
+ ai.add_argument('address',
+ help=_("an alias' e-mail address (local-part@fqdn)"))
+ ai.set_defaults(func=alias_info, scmd='aliasinfo')
+
+ ad = a('aliasdelete', aliases=('ad',),
+ help=_('delete the specified alias e-mail address or one of its '
+ 'destinations'),
+ epilog=fill(_("This subcommand is used to delete one or multiple "
+ "destinations from the alias with the given address.\n\nWhen "
+ "no destination address was specified the alias with all its "
+ "destinations will be deleted.")),
+ formatter_class=RawDescriptionHelpFormatter)
+ ad.add_argument('address',
+ help=_("an alias' e-mail address (local-part@fqdn)"))
+ ad.add_argument('destination', nargs='*',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ ad.set_defaults(func=alias_delete, scmd='aliasdelete')
+
+ ###
+ # catch-all subcommands
+ ###
+ caa = a('catchalladd', aliases=('caa',),
+ help=_('add one or more catch-all destinations for a domain'),
+ epilog=fill(_('This subcommand allows to specify destination '
+ 'addresses for a domain, which shall receive mail addressed '
+ 'to unknown local parts within that domain. Those catch-all '
+ 'aliases hence "catch all" mail to any address in the domain '
+ '(unless a more specific alias, mailbox or relocated entry '
+ 'exists).\n\nWARNING: Catch-all addresses can cause mail '
+ 'server flooding because spammers like to deliver mail to '
+ 'all possible combinations of names, e.g. to all addresses '
+ 'between abba@example.org and zztop@example.org.')),
+ formatter_class=RawDescriptionHelpFormatter)
+ caa.add_argument('fqdn', help=_('a fully qualified domain name'))
+ caa.add_argument('destination', nargs='+',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ caa.set_defaults(func=catchall_add, scmd='catchalladd')
+
+ cai = a('catchallinfo', aliases=('cai',),
+ help=_('show the catch-all destination(s) of the specified '
+ 'domain'),
+ epilog=_('This subcommand displays information about catch-all '
+ 'aliases defined for a domain.'))
+ cai.add_argument('fqdn', help=_('a fully qualified domain name'))
+ cai.set_defaults(func=catchall_info, scmd='catchallinfo')
+
+ cad = a('catchalldelete', aliases=('cad',),
+ help=_("delete the specified catch-all destination or all of a "
+ "domain's destinations"),
+ epilog=_('With this subcommand, catch-all aliases defined for a '
+ 'domain can be removed, either all of them, or those '
+ 'destinations which were specified explicitly.'))
+ cad.add_argument('fqdn', help=_('a fully qualified domain name'))
+ cad.add_argument('destination', nargs='*',
+ help=_("a destination's e-mail address (local-part@fqdn)"))
+ cad.set_defaults(func=catchall_delete, scmd='catchalldelete')
+
+ ###
+ # relocated subcommands
+ ###
+ ra = a('relocatedadd', aliases=('ra',),
+ help=_('create a new record for a relocated user'),
+ epilog=_("A new relocated user can be created with this "
+ "subcommand."))
+ ra.add_argument('address', help=_("a relocated user's e-mail address "
+ "(local-part@fqdn)"))
+ ra.add_argument('newaddress',
+ help=_('e-mail address where the user can be reached now'))
+ ra.set_defaults(func=relocated_add, scmd='relocatedadd')
+
+ ri = a('relocatedinfo', aliases=('ri',),
+ help=_('print information about a relocated user'),
+ epilog=_('This subcommand shows the new address of the relocated '
+ 'user with the given address.'))
+ ri.add_argument('address', help=_("a relocated user's e-mail address "
+ "(local-part@fqdn)"))
+ ri.set_defaults(func=relocated_info, scmd='relocatedinfo')
+
+ rd = a('relocateddelete', aliases=('rd',),
+ help=_('delete the record of the relocated user'),
+ epilog=_('Use this subcommand in order to delete the relocated '
+ 'user with the given address.'))
+ rd.add_argument('address', help=_("a relocated user's e-mail address "
+ "(local-part@fqdn)"))
+ rd.set_defaults(func=relocated_delete, scmd='relocateddelete')
+
+ txt_wrpr.replace_whitespace = old_rw
+ return parser
def _get_order(ctx):
"""returns a tuple with (key, 1||0) tuples. Used by functions, which
get a dict from the handler."""
order = ()
- if ctx.scmd == 'domaininfo':
+ if ctx.args.scmd == 'domaininfo':
order = (('domain name', 0), ('gid', 1), ('domain directory', 0),
('quota limit/user', 0), ('active services', 0),
('transport', 0), ('alias domains', 0), ('accounts', 0),
('aliases', 0), ('relocated', 0), ('catch-all dests', 0))
- elif ctx.scmd == 'userinfo':
- if ctx.argc == 4 and ctx.args[3] != 'aliases' or \
+ elif ctx.args.scmd == 'userinfo':
+ if ctx.args.details in ('du', 'full') or \
ctx.cget('account.disk_usage'):
order = (('address', 0), ('name', 0), ('uid', 1), ('gid', 1),
('home', 0), ('mail_location', 0),
@@ -927,7 +1028,7 @@
('quota storage', 0), ('quota messages', 0),
('transport', 0), ('smtp', 1), ('pop3', 1),
('imap', 1), ('sieve', 1))
- elif ctx.scmd == 'getuser':
+ elif ctx.args.scmd == 'getuser':
order = (('uid', 1), ('gid', 1), ('address', 0))
return order