summaryrefslogtreecommitdiffstats
path: root/ipalib
diff options
context:
space:
mode:
Diffstat (limited to 'ipalib')
-rw-r--r--ipalib/__init__.py60
-rw-r--r--ipalib/cli.py58
-rw-r--r--ipalib/crud.py15
-rw-r--r--ipalib/frontend.py145
-rw-r--r--ipalib/output.py104
-rw-r--r--ipalib/parameters.py22
-rw-r--r--ipalib/plugins/baseldap.py89
-rw-r--r--ipalib/plugins/group.py22
-rw-r--r--ipalib/plugins/hbac.py14
-rw-r--r--ipalib/plugins/host.py29
-rw-r--r--ipalib/plugins/misc.py81
-rw-r--r--ipalib/plugins/passwd.py3
-rw-r--r--ipalib/plugins/pwpolicy.py67
-rw-r--r--ipalib/plugins/service.py3
-rw-r--r--ipalib/plugins/user.py79
-rw-r--r--ipalib/text.py79
16 files changed, 677 insertions, 193 deletions
diff --git a/ipalib/__init__.py b/ipalib/__init__.py
index c1ad760b6..83956e16d 100644
--- a/ipalib/__init__.py
+++ b/ipalib/__init__.py
@@ -135,13 +135,13 @@ implement a ``run()`` method, like this:
... """My example plugin with run()."""
...
... def run(self):
-... return 'My run() method was called!'
+... return dict(result='My run() method was called!')
...
>>> api = create_api()
>>> api.register(my_command)
>>> api.finalize()
>>> api.Command.my_command() # Call your command
-'My run() method was called!'
+{'result': 'My run() method was called!'}
When `frontend.Command.__call__()` is called, it first validates any arguments
and options your command plugin takes (if any) and then calls its ``run()``
@@ -175,10 +175,14 @@ For example, say you have a command plugin like this:
... """Forwarding vs. execution."""
...
... def forward(self):
-... return 'in_server=%r; forward() was called.' % self.env.in_server
+... return dict(
+... result='forward(): in_server=%r' % self.env.in_server
+... )
...
... def execute(self):
-... return 'in_server=%r; execute() was called.' % self.env.in_server
+... return dict(
+... result='execute(): in_server=%r' % self.env.in_server
+... )
...
If ``my_command`` is loaded in a *client* context, ``forward()`` will be
@@ -189,7 +193,7 @@ called:
>>> api.register(my_command)
>>> api.finalize()
>>> api.Command.my_command() # Call your command plugin
-'in_server=False; forward() was called.'
+{'result': 'forward(): in_server=False'}
On the other hand, if ``my_command`` is loaded in a *server* context,
``execute()`` will be called:
@@ -199,7 +203,7 @@ On the other hand, if ``my_command`` is loaded in a *server* context,
>>> api.register(my_command)
>>> api.finalize()
>>> api.Command.my_command() # Call your command plugin
-'in_server=True; execute() was called.'
+{'result': 'execute(): in_server=True'}
Normally there should be no reason to override `frontend.Command.forward()`,
but, as above, it can be done for demonstration purposes. In contrast, there
@@ -312,7 +316,7 @@ Second, we have our frontend plugin, the command:
...
... def execute(self):
... """Implemented against Backend.my_backend"""
-... return self.Backend.my_backend.do_stuff()
+... return dict(result=self.Backend.my_backend.do_stuff())
...
>>> api.register(my_command)
@@ -321,7 +325,7 @@ Lastly, we call ``api.finalize()`` and see what happens when we call
>>> api.finalize()
>>> api.Command.my_command()
-'my_backend.do_stuff() indeed did do stuff!'
+{'result': 'my_backend.do_stuff() indeed did do stuff!'}
When not in a server context, ``my_command.execute()`` never gets called, so
it never tries to access the non-existent backend plugin at
@@ -335,10 +339,10 @@ example:
...
... def execute(self):
... """Same as above."""
-... return self.Backend.my_backend.do_stuff()
+... return dict(result=self.Backend.my_backend.do_stuff())
...
... def forward(self):
-... return 'Just my_command.forward() getting called here.'
+... return dict(result='Just my_command.forward() getting called here.')
...
>>> api.register(my_command)
>>> api.finalize()
@@ -351,7 +355,7 @@ False
And yet we can call ``my_command()``:
>>> api.Command.my_command()
-'Just my_command.forward() getting called here.'
+{'result': 'Just my_command.forward() getting called here.'}
----------------------------------------
@@ -369,24 +373,25 @@ several other commands in a single operation. For example:
...
... def execute(self):
... """Calls command_1(), command_2()"""
-... return '%s; %s.' % (
-... self.Command.command_1(),
-... self.Command.command_2()
+... msg = '%s; %s.' % (
+... self.Command.command_1()['result'],
+... self.Command.command_2()['result'],
... )
+... return dict(result=msg)
>>> class command_1(Command):
... def execute(self):
-... return 'command_1.execute() called'
+... return dict(result='command_1.execute() called')
...
>>> class command_2(Command):
... def execute(self):
-... return 'command_2.execute() called'
+... return dict(result='command_2.execute() called')
...
>>> api.register(meta_command)
>>> api.register(command_1)
>>> api.register(command_2)
>>> api.finalize()
>>> api.Command.meta_command()
-'command_1.execute() called; command_2.execute() called.'
+{'result': 'command_1.execute() called; command_2.execute() called.'}
Because this is quite useful, we are going to revise our golden rule somewhat:
@@ -412,16 +417,18 @@ For example:
... takes_options = (Str('stuff', default=u'documentation'))
...
... def execute(self, programmer, **kw):
-... return '%s, go write more %s!' % (programmer, kw['stuff'])
+... return dict(
+... result='%s, go write more %s!' % (programmer, kw['stuff'])
+... )
...
>>> api = create_api()
>>> api.env.in_server = True
>>> api.register(nudge)
>>> api.finalize()
>>> api.Command.nudge(u'Jason')
-u'Jason, go write more documentation!'
+{'result': u'Jason, go write more documentation!'}
>>> api.Command.nudge(u'Jason', stuff=u'unit tests')
-u'Jason, go write more unit tests!'
+{'result': u'Jason, go write more unit tests!'}
The ``args`` and ``options`` attributes are `plugable.NameSpace` instances
containing a command's arguments and options, respectively, as you can see:
@@ -450,7 +457,7 @@ When calling a command, its positional arguments can also be provided as
keyword arguments, and in any order. For example:
>>> api.Command.nudge(stuff=u'lines of code', programmer=u'Jason')
-u'Jason, go write more lines of code!'
+{'result': u'Jason, go write more lines of code!'}
When a command plugin is called, the values supplied for its parameters are
put through a sophisticated processing pipeline that includes steps for
@@ -582,12 +589,14 @@ For example, say we setup a command like this:
... city='Berlin',
... )
... if key in items:
-... return items[key]
-... return [
+... return dict(result=items[key])
+... items = [
... (k, items[k]) for k in sorted(items, reverse=options['reverse'])
... ]
+... return dict(result=items)
...
... def output_for_cli(self, textui, result, key, **options):
+... result = result['result']
... if key is not None:
... textui.print_plain('%s = %r' % (key, result))
... else:
@@ -738,14 +747,14 @@ For example:
... """Print message of the day."""
...
... def execute(self):
-... return self.env.message
+... return dict(result=self.env.message)
...
>>> api = create_api()
>>> api.bootstrap(in_server=True, message='Hello, world!')
>>> api.register(motd)
>>> api.finalize()
>>> api.Command.motd()
-'Hello, world!'
+{'result': 'Hello, world!'}
Also see the `plugable.API.bootstrap_with_global_options()` method.
@@ -872,6 +881,7 @@ from crud import Create, Retrieve, Update, Delete, Search
from parameters import DefaultFrom, Bool, Flag, Int, Float, Bytes, Str, Password,List
from parameters import BytesEnum, StrEnum, AccessTime, File
from errors import SkipPluginModule
+from text import _, gettext, ngettext
# We can't import the python uuid since it includes ctypes which makes
# httpd throw up when run in in mod_python due to SELinux issues
diff --git a/ipalib/cli.py b/ipalib/cli.py
index 64ace0359..d0feaaea3 100644
--- a/ipalib/cli.py
+++ b/ipalib/cli.py
@@ -312,6 +312,37 @@ class textui(backend.Backend):
for attr in sorted(entry):
print_attr(attr)
+ def print_entries(self, entries, params=None, format='%s: %s', indent=1):
+ assert isinstance(entries, (list, tuple))
+ first = True
+ for entry in entries:
+ if not first:
+ print ''
+ first = False
+ self.print_entry(entry, params, format, indent)
+
+ def print_entry(self, entry, params=None, format='%s: %s', indent=1):
+ """
+ """
+ if isinstance(entry, (list, tuple)):
+ entry = dict(entry)
+ assert isinstance(entry, dict)
+ if params:
+ order = list(params)
+ labels = dict((p.name, p.label) for p in params())
+ else:
+ order = sorted(entry)
+ labels = dict((k, k) for k in order)
+ for key in order:
+ if key not in entry:
+ continue
+ label = labels[key]
+ value = entry[key]
+ if isinstance(value, (list, tuple)):
+ value = ', '.join(value)
+ self.print_indented(format % (label, value), indent)
+
+
def print_dashed(self, string, above=True, below=True, indent=0, dash='-'):
"""
Print a string with a dashed line above and/or below.
@@ -383,6 +414,23 @@ class textui(backend.Backend):
"""
self.print_dashed('%s:' % to_cli(name))
+ def print_header(self, msg, output):
+ self.print_dashed(msg % output)
+
+ def print_summary(self, msg):
+ """
+ Print a summary at the end of a comand's output.
+
+ For example:
+
+ >>> ui = textui()
+ >>> ui.print_summary('Added user "jdoe"')
+ -----------------
+ Added user "jdoe"
+ -----------------
+ """
+ self.print_dashed(msg)
+
def print_count(self, count, singular, plural=None):
"""
Print a summary count.
@@ -408,7 +456,7 @@ class textui(backend.Backend):
``len(count)`` is used as the count.
"""
if type(count) is not int:
- assert type(count) in (list, tuple)
+ assert type(count) in (list, tuple, dict)
count = len(count)
self.print_dashed(
self.choose_number(count, singular, plural)
@@ -515,6 +563,8 @@ class help(frontend.Command):
takes_args = (Bytes('command?'),)
+ has_output = tuple()
+
_PLUGIN_BASE_MODULE = 'ipalib.plugins'
def _get_command_module(self, module):
@@ -614,6 +664,8 @@ class help(frontend.Command):
class console(frontend.Command):
"""Start the IPA interactive Python console."""
+ has_output = tuple()
+
def run(self):
code.interact(
'(Custom IPA interactive Python console)',
@@ -814,8 +866,8 @@ class cli(backend.Executioner):
error = None
while True:
if error is not None:
- print '>>> %s: %s' % (param.cli_name, error)
- raw = self.Backend.textui.prompt(param.cli_name, default)
+ print '>>> %s: %s' % (param.label, error)
+ raw = self.Backend.textui.prompt(param.label, default)
try:
value = param(raw, **kw)
if value is not None:
diff --git a/ipalib/crud.py b/ipalib/crud.py
index 6d1a87f32..173fefc72 100644
--- a/ipalib/crud.py
+++ b/ipalib/crud.py
@@ -20,7 +20,7 @@
Base classes for standard CRUD operations.
"""
-import backend, frontend, parameters
+import backend, frontend, parameters, output
class Create(frontend.Method):
@@ -28,6 +28,8 @@ class Create(frontend.Method):
Create a new entry.
"""
+ has_output = output.standard_entry
+
def get_args(self):
if self.obj.primary_key:
yield self.obj.primary_key.clone(attribute=True)
@@ -53,18 +55,21 @@ class PKQuery(frontend.Method):
yield self.obj.primary_key.clone(attribute=True, query=True)
-
class Retrieve(PKQuery):
"""
Retrieve an entry by its primary key.
"""
+ has_output = output.standard_entry
+
class Update(PKQuery):
"""
Update one or more attributes on an entry.
"""
+ has_output = output.standard_entry
+
def get_options(self):
if self.extra_options_first:
for option in super(Update, self).get_options():
@@ -75,17 +80,22 @@ class Update(PKQuery):
for option in super(Update, self).get_options():
yield option
+
class Delete(PKQuery):
"""
Delete one or more entries.
"""
+ has_output = output.standard_delete
+
class Search(frontend.Method):
"""
Retrieve all entries that match a given search criteria.
"""
+ has_output = output.standard_list_of_entries
+
def get_args(self):
yield parameters.Str('criteria?')
@@ -176,4 +186,3 @@ class CrudBackend(backend.Connectible):
this method should return an empty iterable.
"""
raise NotImplementedError('%s.search()' % self.name)
-
diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index e257a0a25..2c1168a5b 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -27,8 +27,11 @@ from base import lock, check_name, NameSpace
from plugable import Plugin
from parameters import create_param, parse_param_spec, Param, Str, Flag, Password
from util import make_repr
+from output import Output
+from text import _, ngettext
from errors import ZeroArgumentError, MaxArgumentError, OverlapError, RequiresRoot
+from errors import InvocationError
from constants import TYPE_ERROR
@@ -43,6 +46,7 @@ def is_rule(obj):
return callable(obj) and getattr(obj, RULE_FLAG, False) is True
+
class HasParam(Plugin):
"""
Base class for plugins that have `Param` `NameSpace` attributes.
@@ -198,7 +202,7 @@ class HasParam(Plugin):
that consider arbitrary ``api.env`` values.
"""
- def _get_param_iterable(self, name):
+ def _get_param_iterable(self, name, verb='takes'):
"""
Return an iterable of params defined by the attribute named ``name``.
@@ -257,19 +261,19 @@ class HasParam(Plugin):
Also see `HasParam._filter_param_by_context()`.
"""
- takes_name = 'takes_' + name
- takes = getattr(self, takes_name, None)
- if type(takes) is tuple:
- return takes
- if isinstance(takes, (Param, str)):
- return (takes,)
- if callable(takes):
- return takes()
- if takes is None:
+ src_name = verb + '_' + name
+ src = getattr(self, src_name, None)
+ if type(src) is tuple:
+ return src
+ if isinstance(src, (Param, str)):
+ return (src,)
+ if callable(src):
+ return src()
+ if src is None:
return tuple()
raise TypeError(
'%s.%s must be a tuple, callable, or spec; got %r' % (
- self.name, takes_name, takes
+ self.name, src_name, src
)
)
@@ -377,6 +381,15 @@ class Command(HasParam):
output_for_cli = None
obj = None
+ use_output_validation = True
+ output = None
+ has_output = ('result',)
+ output_params = None
+ has_output_params = tuple()
+
+ msg_summary = None
+ msg_truncated = _('Results are truncated, try a more specific search')
+
def __call__(self, *args, **options):
"""
Perform validation and then execute the command.
@@ -396,9 +409,32 @@ class Command(HasParam):
)
self.validate(**params)
(args, options) = self.params_2_args_options(**params)
- result = self.run(*args, **options)
- self.debug('result from %s(): %r', self.name, result)
- return result
+ ret = self.run(*args, **options)
+ if (
+ isinstance(ret, dict)
+ and 'summary' in self.output
+ and 'summary' not in ret
+ ):
+ if self.msg_summary:
+ ret['summary'] = self.msg_summary % ret
+ else:
+ ret['summary'] = None
+ if self.use_output_validation and (self.output or ret is not None):
+ self.validate_output(ret)
+ return ret
+
+ def soft_validate(self, values):
+ errors = dict()
+ for p in self.params():
+ try:
+ value = values.get(p.name)
+ values[p.name] = p(value, **values)
+ except InvocationError, e:
+ errors[p.name] = str(e)
+ return dict(
+ values=values,
+ errors=errors,
+ )
def _repr_iter(self, **params):
"""
@@ -511,10 +547,10 @@ class Command(HasParam):
yield (name, kw[name])
adddict = {}
- if 'setattr' in kw:
+ if kw.get('setattr'):
adddict = self.__convert_2_dict(kw['setattr'], append=False)
- if 'addattr' in kw:
+ if kw.get('addattr'):
adddict.update(self.__convert_2_dict(kw['addattr']))
for name in adddict:
@@ -691,8 +727,24 @@ class Command(HasParam):
sorted(tuple(self.args()) + tuple(self.options()), key=get_key),
sort=False
)
+ self.output = NameSpace(self._iter_output(), sort=False)
+ self._create_param_namespace('output_params')
super(Command, self).finalize()
+ def _iter_output(self):
+ if type(self.has_output) is not tuple:
+ raise TypeError('%s.has_output: need a %r; got a %r: %r' % (
+ self.name, tuple, type(self.has_output), self.has_output)
+ )
+ for (i, o) in enumerate(self.has_output):
+ if isinstance(o, str):
+ o = Output(o)
+ if not isinstance(o, Output):
+ raise TypeError('%s.has_output[%d]: need a %r; got a %r: %r' % (
+ self.name, i, (str, Output), type(o), o)
+ )
+ yield o
+
def get_args(self):
"""
Iterate through parameters for ``Command.args`` namespace.
@@ -741,6 +793,55 @@ class Command(HasParam):
for option in self._get_param_iterable('options'):
yield option
+ def validate_output(self, output):
+ """
+ Validate the return value to make sure it meets the interface contract.
+ """
+ nice = '%s.validate_output()' % self.name
+ if not isinstance(output, dict):
+ raise TypeError('%s: need a %r; got a %r: %r' % (
+ nice, dict, type(output), output)
+ )
+ if len(output) < len(self.output):
+ missing = sorted(set(self.output).difference(output))
+ raise ValueError('%s: missing keys %r in %r' % (
+ nice, missing, output)
+ )
+ if len(output) > len(self.output):
+ extra = sorted(set(output).difference(self.output))
+ raise ValueError('%s: unexpected keys %r in %r' % (
+ nice, extra, output)
+ )
+ for o in self.output():
+ value = output[o.name]
+ if not (o.type is None or isinstance(value, o.type)):
+ raise TypeError('%s:\n output[%r]: need %r; got %r: %r' % (
+ nice, o.name, o.type, type(value), value)
+ )
+ if callable(o.validate):
+ o.validate(self, value)
+
+ def get_output_params(self):
+ for param in self._get_param_iterable('output_params', verb='has'):
+ yield param
+
+ def output_for_cli(self, textui, output, *args, **options):
+ if not isinstance(output, dict):
+ return
+ result = output.get('result')
+ summary = output.get('summary')
+
+ if (summary and isinstance(result, (list, tuple, dict)) and result):
+ textui.print_name(self.name)
+
+ if isinstance(result, (tuple, list)):
+ textui.print_entries(result, self.output_params)
+ elif isinstance(result, dict):
+ textui.print_entry(result, self.output_params)
+
+ if isinstance(summary, unicode):
+ textui.print_summary(summary)
+
class LocalOrRemote(Command):
"""
@@ -972,7 +1073,7 @@ class Method(Attribute, Command):
>>> api = create_api()
>>> class user_add(Method):
... def run(self):
- ... return 'Added the user!'
+ ... return dict(result='Added the user!')
...
>>> class user(Object):
... pass
@@ -987,7 +1088,7 @@ class Method(Attribute, Command):
>>> list(api.Method)
['user_add']
>>> api.Method.user_add() # Will call user_add.run()
- 'Added the user!'
+ {'result': 'Added the user!'}
Second, because `Method` is a subclass of `Command`, the ``user_add``
plugin can also be accessed through the ``api.Command`` namespace:
@@ -995,7 +1096,7 @@ class Method(Attribute, Command):
>>> list(api.Command)
['user_add']
>>> api.Command.user_add() # Will call user_add.run()
- 'Added the user!'
+ {'result': 'Added the user!'}
And third, ``user_add`` can be accessed as an attribute on the ``user``
`Object`:
@@ -1005,7 +1106,7 @@ class Method(Attribute, Command):
>>> list(api.Object.user.methods)
['add']
>>> api.Object.user.methods.add() # Will call user_add.run()
- 'Added the user!'
+ {'result': 'Added the user!'}
The `Attribute` base class implements the naming convention for the
attribute-to-object association. Also see the `Object` and the
@@ -1018,6 +1119,10 @@ class Method(Attribute, Command):
def __init__(self):
super(Method, self).__init__()
+ def get_output_params(self):
+ for param in self.obj.params():
+ yield param
+
class Property(Attribute):
__public__ = frozenset((
diff --git a/ipalib/output.py b/ipalib/output.py
new file mode 100644
index 000000000..425ff977c
--- /dev/null
+++ b/ipalib/output.py
@@ -0,0 +1,104 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty information
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Simple description of return values.
+"""
+
+from inspect import getdoc
+from types import NoneType
+from plugable import ReadOnly, lock
+
+
+class Output(ReadOnly):
+ """
+ Simple description of a member in the return value ``dict``.
+ """
+
+ type = None
+ validate = None
+ doc = None
+
+ def __init__(self, name, type=None, doc=None):
+ self.name = name
+ if type is not None:
+ self.type = type
+ if doc is not None:
+ self.doc = doc
+ lock(self)
+
+ def __repr__(self):
+ return '%s(%r, %r, %r)' % (
+ self.__class__.__name__, self.name, self.type, self.doc,
+ )
+
+
+class Entry(Output):
+ type = dict
+ doc = 'A dictionary representing an LDAP entry'
+
+
+emsg = """%s.validate_output() => %s.validate():
+ output[%r][%d]: need a %r; got a %r: %r"""
+
+class ListOfEntries(Output):
+ type = (list, tuple)
+ doc = 'A list of LDAP entries'
+
+ def validate(self, cmd, entries):
+ assert isinstance(entries, self.type)
+ for (i, entry) in enumerate(entries):
+ if not isinstance(entry, dict):
+ raise TypeError(emsg % (cmd.name, self.__class__.__name__,
+ self.name, i, dict, type(entry), entry)
+ )
+
+
+result = Output('result', doc='All commands should at least have a result')
+
+summary = Output('summary', (unicode, NoneType),
+ 'User-friendly description of action performed'
+)
+
+value = Output('value', unicode,
+ "The primary_key value of the entry, e.g. 'jdoe' for a user"
+)
+
+standard = (result, summary)
+
+standard_entry = (
+ Entry('result'),
+ value,
+ summary,
+)
+
+standard_list_of_entries = (
+ ListOfEntries('result'),
+ Output('count', int, 'Number of entries returned'),
+ Output('truncated', bool, 'True if not all results were returned'),
+ summary,
+)
+
+standard_delete = (
+ Output('result', bool, 'True means the operation was successful'),
+ value,
+ summary,
+)
+
+standard_value = standard_delete
diff --git a/ipalib/parameters.py b/ipalib/parameters.py
index 00880bd2b..b6133f1b1 100644
--- a/ipalib/parameters.py
+++ b/ipalib/parameters.py
@@ -233,8 +233,8 @@ class Param(ReadOnly):
kwargs = (
('cli_name', str, None),
('cli_short_name', str, None),
- ('label', callable, None),
- ('doc', str, ''),
+ ('label', str, None),
+ ('doc', str, None),
('required', bool, True),
('multivalue', bool, False),
('primary_key', bool, False),
@@ -285,10 +285,16 @@ class Param(ReadOnly):
)
)
- # Merge in default for 'cli_name' if not given:
- if kw.get('cli_name', None) is None:
+ # Merge in default for 'cli_name', label, doc if not given:
+ if kw.get('cli_name') is None:
kw['cli_name'] = self.name
+ if kw.get('label') is None:
+ kw['label'] = '<%s>' % self.name
+
+ if kw.get('doc') is None:
+ kw['doc'] = kw['label']
+
# Wrap 'default_from' in a DefaultFrom if not already:
df = kw.get('default_from', None)
if callable(df) and not isinstance(df, DefaultFrom):
@@ -505,14 +511,6 @@ class Param(ReadOnly):
kw.update(overrides)
return self.__class__(self.name, **kw)
- def get_label(self):
- """
- Return translated label using `request.ugettext`.
- """
- if self.label is None:
- return self.cli_name.decode('UTF-8')
- return self.label(ugettext)
-
def normalize(self, value):
"""
Normalize ``value`` using normalizer callback.
diff --git a/ipalib/plugins/baseldap.py b/ipalib/plugins/baseldap.py
index b0ea57380..bd6ce3010 100644
--- a/ipalib/plugins/baseldap.py
+++ b/ipalib/plugins/baseldap.py
@@ -26,6 +26,7 @@ from ipalib import Command, Method, Object
from ipalib import Flag, List, Str
from ipalib.base import NameSpace
from ipalib.cli import to_cli, from_cli
+from ipalib import output
def validate_add_attribute(ugettext, attr):
@@ -178,9 +179,12 @@ class LDAPCreate(crud.Create):
dn = self.post_callback(ldap, dn, entry_attrs, *keys, **options)
self.obj.convert_attribute_members(entry_attrs, *keys, **options)
- return (dn, entry_attrs)
+ return dict(
+ result=entry_attrs,
+ value=keys[0],
+ )
- def output_for_cli(self, textui, entry, *keys, **options):
+ def dont_output_for_cli(self, textui, entry, *keys, **options):
textui.print_name(self.name)
self.obj.print_entry(textui, entry, *keys, **options)
if len(keys) > 1:
@@ -220,6 +224,7 @@ class LDAPRetrieve(LDAPQuery):
"""
Retrieve an LDAP entry.
"""
+
takes_options = (
Flag('raw',
cli_name='raw',
@@ -231,6 +236,8 @@ class LDAPRetrieve(LDAPQuery):
),
)
+ has_output = output.standard_entry
+
def execute(self, *keys, **options):
ldap = self.obj.backend
@@ -248,9 +255,14 @@ class LDAPRetrieve(LDAPQuery):
dn = self.post_callback(ldap, dn, entry_attrs, *keys, **options)
self.obj.convert_attribute_members(entry_attrs, *keys, **options)
- return (dn, entry_attrs)
+ entry_attrs['dn'] = dn
+ return dict(
+ result=entry_attrs,
+ value=keys[0],
+ )
- def output_for_cli(self, textui, entry, *keys, **options):
+
+ def dont_output_for_cli(self, textui, entry, *keys, **options):
textui.print_name(self.name)
self.obj.print_entry(textui, entry, *keys, **options)
@@ -328,9 +340,12 @@ class LDAPUpdate(LDAPQuery, crud.Update):
dn = self.post_callback(ldap, dn, entry_attrs, *keys, **options)
self.obj.convert_attribute_members(entry_attrs, *keys, **options)
- return (dn, entry_attrs)
+ return dict(
+ result=entry_attrs,
+ value=keys[0],
+ )
- def output_for_cli(self, textui, entry, *keys, **options):
+ def dont_output_for_cli(self, textui, entry, *keys, **options):
textui.print_name(self.name)
self.obj.print_entry(textui, entry, *keys, **options)
if len(keys) > 1:
@@ -359,6 +374,8 @@ class LDAPDelete(LDAPQuery):
"""
Delete an LDAP entry and all of its direct subentries.
"""
+ has_output = output.standard_delete
+
def execute(self, *keys, **options):
ldap = self.obj.backend
@@ -384,9 +401,13 @@ class LDAPDelete(LDAPQuery):
result = self.post_callback(ldap, dn, *keys, **options)
- return result
+ return dict(
+ result=result,
+ value=keys[0],
+ )
+
- def output_for_cli(self, textui, result, *keys, **options):
+ def dont_output_for_cli(self, textui, result, *keys, **options):
textui.print_name(self.name)
if len(keys) > 1:
textui.print_dashed(
@@ -454,7 +475,7 @@ class LDAPModMember(LDAPQuery):
failed[attr][ldap_obj_name].append(name)
return (dns, failed)
- def output_for_cli(self, textui, result, *keys, **options):
+ def dont_output_for_cli(self, textui, result, *keys, **options):
(completed, failed, entry) = result
for (attr, objs) in failed.iteritems():
@@ -479,6 +500,19 @@ class LDAPAddMember(LDAPModMember):
member_param_doc = 'comma-separated list of %s to add'
member_count_out = ('%i member added.', '%i members added.')
+ has_output = (
+ output.Entry('result'),
+ output.Output('completed',
+ type=int,
+ doc='Number of members added',
+ ),
+ output.Output('failed',
+ type=dict,
+ doc='Members that could not be added',
+ ),
+ )
+
+
def execute(self, *keys, **options):
ldap = self.obj.backend
@@ -511,7 +545,11 @@ class LDAPAddMember(LDAPModMember):
)
self.obj.convert_attribute_members(entry_attrs, *keys, **options)
- return (completed, failed, (dn, entry_attrs))
+ return dict(
+ completed=completed,
+ failed=failed,
+ result=entry_attrs,
+ )
def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
return dn
@@ -527,6 +565,18 @@ class LDAPRemoveMember(LDAPModMember):
member_param_doc = 'comma-separated list of %s to remove'
member_count_out = ('%i member removed.', '%i members removed.')
+ has_output = (
+ output.Entry('result'),
+ output.Output('completed',
+ type=int,
+ doc='Number of members removed',
+ ),
+ output.Output('failed',
+ type=dict,
+ doc='Members that could not be removed',
+ ),
+ )
+
def execute(self, *keys, **options):
ldap = self.obj.backend
@@ -559,7 +609,11 @@ class LDAPRemoveMember(LDAPModMember):
)
self.obj.convert_attribute_members(entry_attrs, *keys, **options)
- return (completed, failed, (dn, entry_attrs))
+ return dict(
+ completed=completed,
+ failed=failed,
+ result=entry_attrs,
+ )
def pre_callback(self, ldap, dn, found, not_found, *keys, **options):
return dn
@@ -647,9 +701,18 @@ class LDAPSearch(crud.Search):
entries[i][1], *args, **options
)
entries[i] = (dn, entries[i][1])
- return (entries, truncated)
- def output_for_cli(self, textui, result, *args, **options):
+ entries = tuple(e for (dn, e) in entries)
+
+ return dict(
+ result=entries,
+ count=len(entries),
+ truncated=truncated,
+ )
+
+
+
+ def dont_output_for_cli(self, textui, result, *args, **options):
(entries, truncated) = result
textui.print_name(self.name)
diff --git a/ipalib/plugins/group.py b/ipalib/plugins/group.py
index b5cc7ca5a..322f3fa8b 100644
--- a/ipalib/plugins/group.py
+++ b/ipalib/plugins/group.py
@@ -24,6 +24,7 @@ Groups of users
from ipalib import api
from ipalib import Int, Str
from ipalib.plugins.baseldap import *
+from ipalib import _, ngettext
class group(LDAPObject):
@@ -57,16 +58,18 @@ class group(LDAPObject):
takes_params = (
Str('cn',
cli_name='name',
- doc='group name',
+ label='Group name',
primary_key=True,
normalizer=lambda value: value.lower(),
),
Str('description',
cli_name='desc',
- doc='group description',
+ label='Description',
+ doc='Group description',
),
Int('gidnumber?',
cli_name='gid',
+ label='GID',
doc='GID (use this option to set it manually)',
),
)
@@ -78,6 +81,9 @@ class group_add(LDAPCreate):
"""
Create new group.
"""
+
+ msg_summary = _('Added group "%(value)s"')
+
takes_options = LDAPCreate.takes_options + (
Flag('posix',
cli_name='posix',
@@ -90,6 +96,7 @@ class group_add(LDAPCreate):
entry_attrs['objectclass'].append('posixgroup')
return dn
+
api.register(group_add)
@@ -97,6 +104,9 @@ class group_del(LDAPDelete):
"""
Delete group.
"""
+
+ msg_summary = _('Deleted group "%(value)s"')
+
def pre_callback(self, ldap, dn, *keys, **options):
config = ldap.get_ipa_config()[1]
def_primary_group = config.get('ipadefaultprimarygroup', '')
@@ -112,6 +122,9 @@ class group_mod(LDAPUpdate):
"""
Modify group.
"""
+
+ msg_summary = _('Modified group "%(value)s"')
+
takes_options = LDAPUpdate.takes_options + (
Flag('posix',
cli_name='posix',
@@ -138,6 +151,10 @@ class group_find(LDAPSearch):
Search for groups.
"""
+ msg_summary = ngettext(
+ '%(count)d group matched', '%(count)d groups matched', 0
+ )
+
api.register(group_find)
@@ -163,4 +180,3 @@ class group_remove_member(LDAPRemoveMember):
"""
api.register(group_remove_member)
-
diff --git a/ipalib/plugins/hbac.py b/ipalib/plugins/hbac.py
index 4a7cc9407..6dc13f6d5 100644
--- a/ipalib/plugins/hbac.py
+++ b/ipalib/plugins/hbac.py
@@ -35,7 +35,7 @@ class hbac(LDAPObject):
default_attributes = [
'cn', 'accessruletype', 'ipaenabledflag', 'servicename',
'accesstime', 'description',
-
+
]
uuid_attribute = 'ipauniqueid'
attribute_names = {
@@ -128,7 +128,7 @@ class hbac_add(LDAPCreate):
if not dn.startswith('cn='):
msg = 'HBAC rule with name "%s" already exists' % keys[-1]
raise errors.DuplicateEntry(message=msg)
- # HBAC rules are enabled by default
+ # HBAC rules are enabled by default
entry_attrs['ipaenabledflag'] = 'TRUE'
return ldap.make_dn(
entry_attrs, self.obj.uuid_attribute, self.obj.container_dn
@@ -184,7 +184,7 @@ class hbac_enable(LDAPQuery):
except errors.EmptyModlist:
pass
- return True
+ return dict(result=True)
def output_for_cli(self, textui, result, cn):
textui.print_name(self.name)
@@ -208,7 +208,7 @@ class hbac_disable(LDAPQuery):
except errors.EmptyModlist:
pass
- return True
+ return dict(result=True)
def output_for_cli(self, textui, result, cn):
textui.print_name(self.name)
@@ -242,7 +242,7 @@ class hbac_add_accesstime(LDAPQuery):
except errors.EmptyModlist:
pass
- return True
+ return dict(result=True)
def output_for_cli(self, textui, result, cn, **options):
textui.print_name(self.name)
@@ -280,7 +280,7 @@ class hbac_remove_accesstime(LDAPQuery):
except (ValueError, errors.EmptyModlist):
pass
- return True
+ return dict(result=True)
def output_for_cli(self, textui, result, cn, **options):
textui.print_name(self.name)
@@ -351,5 +351,3 @@ class hbac_remove_sourcehost(LDAPRemoveMember):
member_count_out = ('%i object removed.', '%i objects removed.')
api.register(hbac_remove_sourcehost)
-
-
diff --git a/ipalib/plugins/host.py b/ipalib/plugins/host.py
index 8859b5873..dd19362bd 100644
--- a/ipalib/plugins/host.py
+++ b/ipalib/plugins/host.py
@@ -29,6 +29,7 @@ from ipalib import api, errors, util
from ipalib import Str, Flag
from ipalib.plugins.baseldap import *
from ipalib.plugins.service import split_principal
+from ipalib import _, ngettext
def validate_host(ugettext, fqdn):
@@ -72,32 +73,38 @@ class host(LDAPObject):
takes_params = (
Str('fqdn', validate_host,
cli_name='hostname',
- doc='Hostname',
+ label='Hostname',
primary_key=True,
normalizer=lambda value: value.lower(),
),
Str('description?',
cli_name='desc',
+ label='Description',
doc='Description of the host',
),
Str('localityname?',
cli_name='locality',
+ label='Locality',
doc='Locality of the host (Baltimore, MD)',
),
Str('nshostlocation?',
cli_name='location',
+ label='Location',
doc='Location of the host (e.g. Lab 2)',
),
Str('nshardwareplatform?',
cli_name='platform',
+ label='Platform',
doc='Hardware platform of the host (e.g. Lenovo T61)',
),
Str('nsosversion?',
cli_name='os',
+ label='Operating system',
doc='Operating System and version of the host (e.g. Fedora 9)',
),
Str('userpassword?',
cli_name='password',
+ label='User password',
doc='Password used in bulk enrollment',
),
)
@@ -125,6 +132,9 @@ class host_add(LDAPCreate):
"""
Create new host.
"""
+
+ msg_summary = _('Added host "%(value)s"')
+
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
entry_attrs['cn'] = keys[-1]
entry_attrs['serverhostname'] = keys[-1].split('.', 1)[0]
@@ -147,16 +157,21 @@ class host_del(LDAPDelete):
"""
Delete host.
"""
+
+ msg_summary = _('Deleted host "%(value)s"')
+
def pre_callback(self, ldap, dn, *keys, **options):
# Remove all service records for this host
truncated = True
while truncated:
try:
- (services, truncated) = api.Command['service_find'](keys[-1])
+ ret = api.Command['service_find'](keys[-1])
+ truncated = ret['truncated']
+ services = ret['result']
except errors.NotFound:
break
else:
- for (dn_, entry_attrs) in services:
+ for entry_attrs in services:
principal = entry_attrs['krbprincipalname'][0]
(service, hostname, realm) = split_principal(principal)
if hostname.lower() == keys[-1]:
@@ -170,6 +185,9 @@ class host_mod(LDAPUpdate):
"""
Modify host.
"""
+
+ msg_summary = _('Modified host "%(value)s"')
+
takes_options = LDAPUpdate.takes_options + (
Str('krbprincipalname?',
cli_name='principalname',
@@ -201,6 +219,10 @@ class host_find(LDAPSearch):
Search for hosts.
"""
+ msg_summary = ngettext(
+ '%(count)d host matched', '%(count)d hosts matched'
+ )
+
api.register(host_find)
@@ -210,4 +232,3 @@ class host_show(LDAPRetrieve):
"""
api.register(host_show)
-
diff --git a/ipalib/plugins/misc.py b/ipalib/plugins/misc.py
index 8bf9d81fd..0584654f7 100644
--- a/ipalib/plugins/misc.py
+++ b/ipalib/plugins/misc.py
@@ -22,18 +22,39 @@ Misc plugins
"""
import re
-from ipalib import api, LocalOrRemote
-
-
+from ipalib import api, LocalOrRemote, _, ngettext
+from ipalib.output import Output, summary
# FIXME: We should not let env return anything in_server
# when mode == 'production'. This would allow an attacker to see the
# configuration of the server, potentially revealing compromising
# information. However, it's damn handy for testing/debugging.
+
+
class env(LocalOrRemote):
"""Show environment variables"""
- takes_args = ('variables*',)
+ msg_summary = _('%(count)d variables')
+
+ takes_args = (
+ 'variables*',
+ )
+
+ has_output = (
+ Output('result',
+ type=dict,
+ doc='Dictionary mapping variable name to value',
+ ),
+ Output('total',
+ type=int,
+ doc='Total number of variables env (>= count)',
+ ),
+ Output('count',
+ type=int,
+ doc='Number of variables returned (<= total)',
+ ),
+ summary,
+ )
def __find_keys(self, variables):
keys = set()
@@ -52,20 +73,18 @@ class env(LocalOrRemote):
keys = self.env
else:
keys = self.__find_keys(variables)
- return dict(
- (key, self.env[key]) for key in keys
+ ret = dict(
+ result=dict(
+ (key, self.env[key]) for key in keys
+ ),
+ count=len(keys),
+ total=len(self.env),
)
-
- def output_for_cli(self, textui, result, variables, **options):
- if len(result) == 0:
- return
- result = tuple((k, result[k]) for k in sorted(result))
- if len(result) == 1:
- textui.print_keyval(result)
- return
- textui.print_name(self.name)
- textui.print_keyval(result)
- textui.print_count(result, '%d variables')
+ if len(keys) > 1:
+ ret['summary'] = self.msg_summary % ret
+ else:
+ ret['summary'] = None
+ return ret
api.register(env)
@@ -73,18 +92,26 @@ api.register(env)
class plugins(LocalOrRemote):
"""Show all loaded plugins"""
+ msg_summary = ngettext(
+ '%(count)d plugin loaded', '%(count)d plugins loaded'
+ )
+
+ has_output = (
+ Output('result', dict, 'Dictionary mapping plugin names to bases'),
+ Output('count',
+ type=int,
+ doc='Number of plugins loaded',
+ ),
+ summary,
+ )
+
def execute(self, **options):
plugins = sorted(self.api.plugins, key=lambda o: o.plugin)
- return tuple(
- (p.plugin, p.bases) for p in plugins
+ return dict(
+ result=dict(
+ (p.plugin, p.bases) for p in plugins
+ ),
+ count=len(plugins),
)
- def output_for_cli(self, textui, result, **options):
- textui.print_name(self.name)
- for (plugin, bases) in result:
- textui.print_indented(
- '%s: %s' % (plugin, ', '.join(bases))
- )
- textui.print_count(result, '%d plugin loaded', '%s plugins loaded')
-
api.register(plugins)
diff --git a/ipalib/plugins/passwd.py b/ipalib/plugins/passwd.py
index fbc12263c..6c509c2c7 100644
--- a/ipalib/plugins/passwd.py
+++ b/ipalib/plugins/passwd.py
@@ -67,7 +67,7 @@ class passwd(Command):
ldap.modify_password(dn, password)
- return True
+ return dict(result=True)
def output_for_cli(self, textui, result, principal, password):
assert password is None
@@ -75,4 +75,3 @@ class passwd(Command):
textui.print_dashed('Changed password for "%s."' % principal)
api.register(passwd)
-
diff --git a/ipalib/plugins/pwpolicy.py b/ipalib/plugins/pwpolicy.py
index 5a07c880a..faf036418 100644
--- a/ipalib/plugins/pwpolicy.py
+++ b/ipalib/plugins/pwpolicy.py
@@ -25,6 +25,7 @@ Password policy
from ipalib import api, crud, errors
from ipalib import Command, Object
from ipalib import Int, Str
+from ipalib import output
from ldap.functions import explode_dn
_fields = {
@@ -54,6 +55,7 @@ def _convert_time_on_input(entry_attrs):
if 'krbminpwdlife' in entry_attrs:
entry_attrs['krbminpwdlife'] = entry_attrs['krbminpwdlife'] * 3600
+
def make_cos_entry(group, cospriority=None):
"""
Make the CoS dn and entry for this group.
@@ -64,9 +66,10 @@ def make_cos_entry(group, cospriority=None):
"""
try:
- (groupdn, group_attrs) = api.Command['group_show'](group)
+ entry = api.Command['group_show'](group)['result']
except errors.NotFound:
raise errors.NotFound(reason="group '%s' does not exist" % group)
+ groupdn = entry['dn']
cos_entry = {}
if cospriority:
@@ -76,6 +79,7 @@ def make_cos_entry(group, cospriority=None):
return (cos_dn, cos_entry)
+
def make_policy_entry(group_cn, policy_entry):
"""
Make the krbpwdpolicy dn and entry for this group.
@@ -98,6 +102,7 @@ def make_policy_entry(group_cn, policy_entry):
return (policy_dn, policy_entry)
+
class pwpolicy(Object):
"""
Password Policy object.
@@ -138,6 +143,7 @@ class pwpolicy(Object):
api.register(pwpolicy)
+
class pwpolicy_add(crud.Create):
"""
Create a new password policy associated with a group.
@@ -150,6 +156,7 @@ class pwpolicy_add(crud.Create):
),
Int('cospriority',
cli_name='priority',
+ label='Priority',
doc='Priority of the policy. Higher number equals higher priority',
minvalue=0,
attribute=True,
@@ -182,22 +189,12 @@ class pwpolicy_add(crud.Create):
_convert_time_for_output(entry_attrs)
- return (dn, entry_attrs)
-
- def output_for_cli(self, textui, result, *args, **options):
-# textui.print_name(self.name)
-# textui.print_dashed("Added policy for '%s'." % options['group'])
- (dn, entry_attrs) = result
-
- textui.print_name(self.name)
- textui.print_plain('Password policy:')
- for (k, v) in _fields.iteritems():
- if k in entry_attrs:
- textui.print_attribute(v, entry_attrs[k])
- textui.print_dashed('Modified password policy.')
+ entry_attrs['dn'] = dn
+ return dict(result=entry_attrs, value=group_cn)
api.register(pwpolicy_add)
+
class pwpolicy_mod(crud.Update):
"""
Modify password policy.
@@ -215,6 +212,10 @@ class pwpolicy_mod(crud.Update):
),
)
+ has_output = (
+ output.Entry('result'),
+ )
+
def execute(self, *args, **options):
assert 'dn' not in options
ldap = self.api.Backend.ldap2
@@ -235,24 +236,16 @@ class pwpolicy_mod(crud.Update):
_convert_time_for_output(entry_attrs)
- return (dn, entry_attrs)
-
- def output_for_cli(self, textui, result, *args, **options):
- (dn, entry_attrs) = result
-
- textui.print_name(self.name)
- textui.print_plain('Password policy:')
- for (k, v) in _fields.iteritems():
- if k in entry_attrs:
- textui.print_attribute(v, entry_attrs[k])
- textui.print_dashed('Modified password policy.')
+ return dict(result=entry_attrs)
api.register(pwpolicy_mod)
+
class pwpolicy_del(crud.Delete):
"""
Delete a group password policy.
"""
+
takes_options = (
Str('group',
doc='Group to remove policy from',
@@ -278,12 +271,10 @@ class pwpolicy_del(crud.Delete):
ldap.delete_entry(policy_dn, normalize=False)
ldap.delete_entry(cos_dn, normalize=False)
-
- return True
-
- def output_for_cli(self, textui, result, *args, **options):
- textui.print_name(self.name)
- textui.print_dashed('Deleted policy "%s".' % options['group'])
+ return dict(
+ result=True,
+ value=group_cn,
+ )
api.register(pwpolicy_del)
@@ -292,6 +283,7 @@ class pwpolicy_show(Command):
"""
Display password policy.
"""
+
takes_options = (
Str('group?',
doc='Group to display policy',
@@ -300,6 +292,7 @@ class pwpolicy_show(Command):
doc='Display policy applied to a given user',
),
)
+
def execute(self, *args, **options):
ldap = self.api.Backend.ldap2
@@ -333,16 +326,6 @@ class pwpolicy_show(Command):
entry_attrs['group'] = 'global'
_convert_time_for_output(entry_attrs)
- return (dn, entry_attrs)
-
- def output_for_cli(self, textui, result, *args, **options):
- (dn, entry_attrs) = result
-
- textui.print_name(self.name)
- textui.print_plain('Password policy:')
- for (k, v) in _fields.iteritems():
- if k in entry_attrs:
- textui.print_attribute(v, entry_attrs[k])
+ return dict(result=entry_attrs)
api.register(pwpolicy_show)
-
diff --git a/ipalib/plugins/service.py b/ipalib/plugins/service.py
index 5b0119151..f65ab3ebd 100644
--- a/ipalib/plugins/service.py
+++ b/ipalib/plugins/service.py
@@ -149,7 +149,7 @@ class service_add(LDAPCreate):
raise errors.HostService()
try:
- (hostdn, hostentry) = api.Command['host_show'](hostname, **{})
+ api.Command['host_show'](hostname)
except errors.NotFound:
raise errors.NotFound(reason="The host '%s' does not exist to add a service to." % hostname)
@@ -267,4 +267,3 @@ class service_remove_host(LDAPRemoveMember):
member_attributes = ['managedby']
api.register(service_remove_host)
-
diff --git a/ipalib/plugins/user.py b/ipalib/plugins/user.py
index 643531305..44b0f7d5e 100644
--- a/ipalib/plugins/user.py
+++ b/ipalib/plugins/user.py
@@ -24,6 +24,7 @@ Users (Identity)
from ipalib import api, errors
from ipalib import Flag, Int, Password, Str
from ipalib.plugins.baseldap import *
+from ipalib import _, ngettext
class user(LDAPObject):
@@ -68,57 +69,59 @@ class user(LDAPObject):
}
takes_params = (
+ Str('uid',
+ cli_name='login',
+ label='User login',
+ primary_key=True,
+ default_from=lambda givenname, sn: givenname[0] + sn,
+ normalizer=lambda value: value.lower(),
+ ),
Str('givenname',
cli_name='first',
- doc='First name',
+ label='First name',
),
Str('sn',
cli_name='last',
- doc='Last name',
+ label='Last name',
),
- Str('uid',
- cli_name='user',
- doc='Login name',
- primary_key=True,
- default_from=lambda givenname, sn: givenname[0] + sn,
- normalizer=lambda value: value.lower(),
+ Str('homedirectory?',
+ cli_name='homedir',
+ label='Home directory',
+ default_from=lambda uid: '/home/%s' % uid,
),
Str('gecos?',
- doc='GECOS field',
+ label='GECOS field',
default_from=lambda uid: uid,
autofill=True,
),
- Str('homedirectory?',
- cli_name='homedir',
- doc='Home directory',
- default_from=lambda uid: '/home/%s' % uid,
- ),
Str('loginshell?',
cli_name='shell',
+ label='Login shell',
default=u'/bin/sh',
- doc='login shell',
),
Str('krbprincipalname?',
cli_name='principal',
- doc='Kerberos principal name',
+ label='Kerberos principal',
default_from=lambda uid: '%s@%s' % (uid, api.env.realm),
autofill=True,
),
Str('mail?',
cli_name='email',
- doc='e-mail address',
+ label='Email address',
),
Password('userpassword?',
cli_name='password',
- doc='password',
+ label='Password',
+ doc='Set the user password',
),
Int('uidnumber?',
cli_name='uid',
+ label='UID',
doc='UID (use this option to set it manually)',
),
Str('street?',
cli_name='street',
- doc='street address',
+ label='Street address',
),
)
@@ -129,6 +132,9 @@ class user_add(LDAPCreate):
"""
Create new user.
"""
+
+ msg_summary = _('Added user "%(value)s"')
+
def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
config = ldap.get_ipa_config()[1]
entry_attrs.setdefault('loginshell', config.get('ipadefaultloginshell'))
@@ -171,6 +177,9 @@ class user_del(LDAPDelete):
"""
Delete user.
"""
+
+ msg_summary = _('Deleted user "%(value)s"')
+
def pre_callback(self, ldap, dn, *keys, **options):
if keys[-1] == 'admin':
raise errors.ExecutionError('Cannot delete user "admin".')
@@ -188,6 +197,8 @@ class user_mod(LDAPUpdate):
Modify user.
"""
+ msg_summary = _('Modified user "%(value)s"')
+
api.register(user_mod)
@@ -196,6 +207,10 @@ class user_find(LDAPSearch):
Search for users.
"""
+ msg_summary = ngettext(
+ '%(count)d user matched', '%(count)d users matched', 0
+ )
+
api.register(user_find)
@@ -211,6 +226,10 @@ class user_lock(LDAPQuery):
"""
Lock user account.
"""
+
+ has_output = output.standard_value
+ msg_summary = _('Locked user "%(value)s"')
+
def execute(self, *keys, **options):
ldap = self.obj.backend
@@ -221,11 +240,10 @@ class user_lock(LDAPQuery):
except errors.AlreadyInactive:
pass
- return True
-
- def output_for_cli(self, textui, result, *keys, **options):
- textui.print_name(self.name)
- textui.print_dashed('Locked user "%s".' % keys[-1])
+ return dict(
+ result=True,
+ value=keys[0],
+ )
api.register(user_lock)
@@ -234,6 +252,10 @@ class user_unlock(LDAPQuery):
"""
Unlock user account.
"""
+
+ has_output = output.standard_value
+ msg_summary = _('Unlocked user "%(value)s"')
+
def execute(self, *keys, **options):
ldap = self.obj.backend
@@ -244,10 +266,9 @@ class user_unlock(LDAPQuery):
except errors.AlreadyActive:
pass
- return True
-
- def output_for_cli(self, textui, result, *keys, **options):
- textui.print_name(self.name)
- textui.print_dashed('Unlocked user "%s".' % keys[-1])
+ return dict(
+ result=True,
+ value=keys[0],
+ )
api.register(user_unlock)
diff --git a/ipalib/text.py b/ipalib/text.py
new file mode 100644
index 000000000..0c8684025
--- /dev/null
+++ b/ipalib/text.py
@@ -0,0 +1,79 @@
+# Authors:
+# Jason Gerard DeRose <jderose@redhat.com>
+#
+# Copyright (C) 2009 Red Hat
+# see file 'COPYING' for use and warranty contextrmation
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License as
+# published by the Free Software Foundation; version 2 only
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+"""
+Thread-local lazy gettext service.
+
+TODO: This aren't hooked up into gettext yet, they currently just provide
+placeholders for the rest of the code.
+"""
+
+
+class LazyText(object):
+ def __init__(self, domain, localedir):
+ self.domain = domain
+ self.localedir = localedir
+
+
+class Gettext(LazyText):
+ def __init__(self, msg, domain, localedir):
+ self.msg = msg
+ super(Gettext, self).__init__(domain, localedir)
+
+ def __unicode__(self):
+ return self.msg.decode('utf-8')
+
+ def __mod__(self, value):
+ return self.__unicode__() % value
+
+
+class NGettext(LazyText):
+ def __init__(self, singular, plural, domain, localedir):
+ self.singular = singular
+ self.plural = plural
+ super(NGettext, self).__init__(domain, localedir)
+
+ def __mod__(self, kw):
+ count = kw['count']
+ return self(count) % kw
+
+ def __call__(self, count):
+ if count == 1:
+ return self.singular.decode('utf-8')
+ return self.plural.decode('utf-8')
+
+
+class gettext_factory(object):
+ def __init__(self, domain='ipa', localedir=None):
+ self.domain = domain
+ self.localedir = localedir
+
+ def __call__(self, msg):
+ return Gettext(msg, self.domain, self.localedir)
+
+
+class ngettext_factory(gettext_factory):
+ def __call__(self, singular, plural, count=0):
+ return NGettext(singular, plural, self.domain, self.localedir)
+
+
+# Process wide factories:
+gettext = gettext_factory()
+_ = gettext
+ngettext = ngettext_factory()