schema: add object class schema

Support object classes defined by object plugins in API schema.

Added new commands `class-show` and `class-find` to retrieve information
about object classes. `param-show` and `param-find` now support both
commands and classes.

https://fedorahosted.org/freeipa/ticket/4739

Reviewed-By: David Kupka <dkupka@redhat.com>
This commit is contained in:
Jan Cholasta 2016-06-16 13:21:17 +02:00
parent 3ec7a52aea
commit ec1b3e71b2
4 changed files with 267 additions and 97 deletions

24
API.txt
View File

@ -843,6 +843,26 @@ option: Str('version?')
output: Entry('result') output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>]) output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: class_find
args: 1,4,4
arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('pkey_only?', autofill=True, default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('version?')
output: Output('count', type=[<type 'int'>])
output: ListOfEntries('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>])
command: class_show
args: 1,3,3
arg: Str('name')
option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False)
option: Str('version?')
output: Entry('result')
output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value')
command: command_defaults command: command_defaults
args: 1,3,1 args: 1,3,1
arg: Str('name') arg: Str('name')
@ -3324,7 +3344,7 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: PrimaryKey('value') output: PrimaryKey('value')
command: param_find command: param_find
args: 2,4,4 args: 2,4,4
arg: Str('commandname', cli_name='command') arg: Str('metaobjectname', cli_name='metaobject')
arg: Str('criteria?') arg: Str('criteria?')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('pkey_only?', autofill=True, default=False) option: Flag('pkey_only?', autofill=True, default=False)
@ -3336,7 +3356,7 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
output: Output('truncated', type=[<type 'bool'>]) output: Output('truncated', type=[<type 'bool'>])
command: param_show command: param_show
args: 2,3,3 args: 2,3,3
arg: Str('commandname', cli_name='command') arg: Str('metaobjectname', cli_name='metaobject')
arg: Str('name') arg: Str('name')
option: Flag('all', autofill=True, cli_name='all', default=False) option: Flag('all', autofill=True, cli_name='all', default=False)
option: Flag('raw', autofill=True, cli_name='raw', default=False) option: Flag('raw', autofill=True, cli_name='raw', default=False)

View File

@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
# # # #
######################################################## ########################################################
IPA_API_VERSION_MAJOR=2 IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=188 IPA_API_VERSION_MINOR=189
# Last change: mbabinsk - extend server-del to perform full master removal # Last change: schema: add object class schema

View File

@ -10,8 +10,8 @@ import types
import six import six
from ipaclient.plugins.rpcclient import rpcclient from ipaclient.plugins.rpcclient import rpcclient
from ipalib import Command
from ipalib import parameters, plugable from ipalib import parameters, plugable
from ipalib.frontend import Command, Object
from ipalib.output import Output from ipalib.output import Output
from ipalib.parameters import Bool, DefaultFrom, Flag, Password, Str from ipalib.parameters import Bool, DefaultFrom, Flag, Password, Str
from ipalib.text import ConcatenatedLazyText from ipalib.text import ConcatenatedLazyText
@ -226,8 +226,23 @@ def _create_command(schema):
return command return command
class _LazySchemaCommand(object): def _create_class(schema):
def __init__(self, schema): cls = {}
cls['name'] = str(schema['name'])
if 'doc' in schema:
cls['doc'] = ConcatenatedLazyText(schema['doc'])
if 'topic_topic' in schema:
cls['topic'] = str(schema['topic_topic'])
else:
cls['topic'] = None
cls['takes_params'] = tuple(_create_param(s) for s in schema['params'])
return cls
class _LazySchemaPlugin(object):
def __init__(self, base, schema):
self.__base = base
self.__schema = schema self.__schema = schema
self.__class = None self.__class = None
self.__module__ = None self.__module__ = None
@ -236,21 +251,32 @@ class _LazySchemaCommand(object):
def name(self): def name(self):
return str(self.__schema['name']) return str(self.__schema['name'])
bases = (_SchemaCommand,) @property
def bases(self):
if self.__base is Command:
return (_SchemaCommand,)
else:
return (self.__base,)
def __call__(self, api): def __call__(self, api):
if self.__class is None: if self.__class is None:
command = _create_command(self.__schema) if self.__base is Command:
name = command.pop('name') metaobject = _create_command(self.__schema)
command = type(name, (_SchemaCommand,), command) else:
command.__module__ = self.__module__ metaobject = _create_class(self.__schema)
self.__class = command metaobject = type(self.name, self.bases, metaobject)
metaobject.__module__ = self.__module__
self.__class = metaobject
return self.__class(api) return self.__class(api)
def _create_commands(schema): def _create_commands(schema):
return [_LazySchemaCommand(s) for s in schema] return [_LazySchemaPlugin(Command, s) for s in schema]
def _create_classes(schema):
return [_LazySchemaPlugin(Object, s) for s in schema]
def _create_topic(schema): def _create_topic(schema):
@ -289,6 +315,7 @@ def get_package(api):
client.disconnect() client.disconnect()
commands = _create_commands(schema['commands']) commands = _create_commands(schema['commands'])
classes = _create_classes(schema['classes'])
topics = _create_topics(schema['topics']) topics = _create_topics(schema['topics'])
package = types.ModuleType(package_name) package = types.ModuleType(package_name)
@ -308,6 +335,11 @@ def get_package(api):
command = module.register()(command) command = module.register()(command)
setattr(module, command.name, command) setattr(module, command.name, command)
for cls in classes:
cls.__module__ = module_name
cls = module.register()(cls)
setattr(module, cls.name, command)
for topic in topics: for topic in topics:
name = topic.pop('name') name = topic.pop('name')
module_name = '.'.join((package_name, name)) module_name = '.'.join((package_name, name))

View File

@ -8,6 +8,7 @@ import sys
import six import six
from .baseldap import LDAPObject
from ipalib import errors from ipalib import errors
from ipalib.crud import PKQuery, Retrieve, Search from ipalib.crud import PKQuery, Retrieve, Search
from ipalib.frontend import Command, Local, Method, Object from ipalib.frontend import Command, Local, Method, Object
@ -130,8 +131,41 @@ class MetaSearch(BaseMetaSearch):
@register() @register()
class command(MetaObject): class metaobject(MetaObject):
takes_params = BaseMetaObject.takes_params + ( takes_params = MetaObject.takes_params + (
Str(
'params_param*',
label=_("Parameters"),
flags={'no_search'},
),
)
def _iter_params(self, metaobj):
raise NotImplementedError()
def _get_obj(self, metaobj, all=False, **kwargs):
obj = dict()
obj['name'] = unicode(metaobj.name)
if all:
params = [unicode(p.name) for p in self._iter_params(metaobj)]
if params:
obj['params_param'] = params
return obj
class metaobject_show(MetaRetrieve):
pass
class metaobject_find(MetaSearch):
pass
@register()
class command(metaobject):
takes_params = metaobject.takes_params + (
Str( Str(
'args_param*', 'args_param*',
label=_("Arguments"), label=_("Arguments"),
@ -154,42 +188,49 @@ class command(MetaObject):
), ),
) )
def _get_obj(self, command, **kwargs): def _iter_params(self, cmd):
obj = dict() for arg in cmd.args():
obj['name'] = unicode(command.name) yield arg
for option in cmd.options():
if option.name == 'version':
continue
yield option
if command.doc: def _get_obj(self, cmd, **kwargs):
obj['doc'] = unicode(command.doc) obj = super(command, self)._get_obj(cmd, **kwargs)
if command.topic: if cmd.doc:
obj['doc'] = unicode(cmd.doc)
if cmd.topic:
try: try:
topic = self.api.Object.topic.retrieve(unicode(command.topic)) topic = self.api.Object.topic.retrieve(unicode(cmd.topic))
except errors.NotFound: except errors.NotFound:
pass pass
else: else:
obj['topic_topic'] = topic['name'] obj['topic_topic'] = topic['name']
if command.NO_CLI: if cmd.NO_CLI:
obj['no_cli'] = True obj['no_cli'] = True
if len(command.args): if len(cmd.args):
obj['args_param'] = tuple(unicode(n) for n in command.args) obj['args_param'] = tuple(unicode(n) for n in cmd.args)
if len(command.options): if len(cmd.options):
obj['options_param'] = tuple( obj['options_param'] = tuple(
unicode(n) for n in command.options if n != 'version') unicode(n) for n in cmd.options if n != 'version')
if len(command.output_params): if len(cmd.output_params):
obj['output_params_param'] = tuple( obj['output_params_param'] = tuple(
unicode(n) for n in command.output_params) unicode(n) for n in cmd.output_params)
return obj return obj
def _retrieve(self, name, **kwargs): def _retrieve(self, name, **kwargs):
try: try:
command = self.api.Command[name] cmd = self.api.Command[name]
if not isinstance(command, Local): if not isinstance(cmd, Local):
return command return cmd
except KeyError: except KeyError:
pass pass
@ -200,18 +241,18 @@ class command(MetaObject):
) )
def _search(self, **kwargs): def _search(self, **kwargs):
for command in self.api.Command(): for cmd in self.api.Command():
if not isinstance(command, Local): if not isinstance(cmd, Local):
yield command yield cmd
@register() @register()
class command_show(MetaRetrieve): class command_show(metaobject_show):
__doc__ = _("Display information about a command.") __doc__ = _("Display information about a command.")
@register() @register()
class command_find(MetaSearch): class command_find(metaobject_find):
__doc__ = _("Search for commands.") __doc__ = _("Search for commands.")
@ -235,6 +276,52 @@ class command_defaults(PKQuery):
return dict(result=result) return dict(result=result)
@register()
class class_(metaobject):
name = 'class'
def _iter_params(self, metaobj):
for param in metaobj.params():
yield param
if isinstance(metaobj, LDAPObject) and 'show' in metaobj.methods:
members = (
'{}_{}'.format(attr_name, obj_name)
for attr_name, obj_names in metaobj.attribute_members.items()
for obj_name in obj_names)
passwords = (name for _, name in metaobj.password_attributes)
names = set(itertools.chain(members, passwords))
for param in metaobj.methods.show.output_params():
if param.name in names and param.name not in metaobj.params:
yield param
def _retrieve(self, name, **kwargs):
try:
return self.api.Object[name]
except KeyError:
pass
raise errors.NotFound(
reason=_("%(pkey)s: %(oname)s not found") % {
'pkey': name, 'oname': self.name,
}
)
def _search(self, **kwargs):
return self.api.Object()
@register()
class class_show(metaobject_show):
__doc__ = _("Display information about a class.")
@register()
class class_find(metaobject_find):
__doc__ = _("Search for classes.")
@register() @register()
class topic_(MetaObject): class topic_(MetaObject):
name = 'topic' name = 'topic'
@ -328,13 +415,17 @@ class BaseParam(BaseMetaObject):
), ),
) )
def _split_search_args(self, commandname, criteria=None): @property
return [commandname], criteria def parent(self):
raise AttributeError('parent')
def _split_search_args(self, parent_name, criteria=None):
return [parent_name], criteria
class BaseParamMethod(Method): class BaseParamMethod(Method):
def get_args(self): def get_args(self):
parent = self.api.Object.command parent = self.obj.parent
parent_key = parent.primary_key parent_key = parent.primary_key
yield parent_key.clone_rename( yield parent_key.clone_rename(
parent.name + parent_key.name, parent.name + parent_key.name,
@ -449,6 +540,11 @@ class param(BaseParam):
label=_("No option"), label=_("No option"),
flags={'no_search'}, flags={'no_search'},
), ),
Bool(
'no_output?',
label=_("No output"),
flags={'no_search'},
),
Bool( Bool(
'suppress_empty?', 'suppress_empty?',
label=_("Suppress empty"), label=_("Suppress empty"),
@ -461,7 +557,13 @@ class param(BaseParam):
), ),
) )
def _get_obj(self, param, **kwargs): @property
def parent(self):
return self.api.Object.metaobject
def _get_obj(self, metaobj_param, **kwargs):
metaobj, param = metaobj_param
obj = dict() obj = dict()
obj['name'] = unicode(param.name) obj['name'] = unicode(param.name)
@ -480,56 +582,62 @@ class param(BaseParam):
obj['sensitive'] = True obj['sensitive'] = True
for key, value in param._Param__clonekw.items(): for key, value in param._Param__clonekw.items():
if key in ('alwaysask', if key in ('doc',
'autofill', 'label'):
'confirm',
'sortorder'):
obj[key] = value
elif key in ('cli_metavar',
'cli_name',
'doc',
'hint',
'label',
'option_group'):
obj[key] = unicode(value) obj[key] = unicode(value)
elif key == 'default': elif key in ('exclude',
if param.multivalue:
obj[key] = [unicode(v) for v in value]
else:
obj[key] = [unicode(value)]
elif key == 'default_from':
obj['default_from_param'] = list(unicode(k)
for k in value.keys)
elif key in ('deprecated_cli_aliases',
'exclude',
'include'): 'include'):
obj[key] = list(unicode(v) for v in value) obj[key] = list(unicode(v) for v in value)
elif key in ('exponential', if isinstance(metaobj, Command):
'normalizer', if key in ('alwaysask',
'only_absolute', 'autofill',
'precision'): 'confirm',
obj['no_convert'] = True 'sortorder'):
obj[key] = value
elif key in ('cli_metavar',
'cli_name',
'hint',
'option_group'):
obj[key] = unicode(value)
elif key == 'default':
if param.multivalue:
obj[key] = [unicode(v) for v in value]
else:
obj[key] = [unicode(value)]
elif key == 'default_from':
obj['default_from_param'] = list(unicode(k)
for k in value.keys)
elif key == 'deprecated_cli_aliases':
obj[key] = list(unicode(v) for v in value)
elif key in ('exponential',
'normalizer',
'only_absolute',
'precision'):
obj['no_convert'] = True
for flag in (param.flags or []): for flag in (param.flags or []):
if flag in ('dnsrecord_extra', if flag in ('no_output',
'dnsrecord_part',
'no_option',
'suppress_empty'): 'suppress_empty'):
obj[flag] = True obj[flag] = True
if isinstance(metaobj, Command):
if flag in ('dnsrecord_extra',
'dnsrecord_part',
'no_option'):
obj[flag] = True
return obj return obj
def _retrieve(self, commandname, name, **kwargs): def _retrieve(self, metaobjectname, name, **kwargs):
command = self.api.Command[commandname] try:
metaobj = self.api.Command[metaobjectname]
plugin = self.api.Object['command']
except KeyError:
metaobj = self.api.Object[metaobjectname]
plugin = self.api.Object['class']
if name != 'version': for param in plugin._iter_params(metaobj):
try: if param.name == name:
return command.params[name] return metaobj, param
except KeyError:
try:
return command.output_params[name]
except KeyError:
pass
raise errors.NotFound( raise errors.NotFound(
reason=_("%(pkey)s: %(oname)s not found") % { reason=_("%(pkey)s: %(oname)s not found") % {
@ -537,15 +645,15 @@ class param(BaseParam):
} }
) )
def _search(self, commandname, **kwargs): def _search(self, metaobjectname, **kwargs):
command = self.api.Command[commandname] try:
metaobj = self.api.Command[metaobjectname]
plugin = self.api.Object['command']
except KeyError:
metaobj = self.api.Object[metaobjectname]
plugin = self.api.Object['class']
result = itertools.chain( return ((metaobj, param) for param in plugin._iter_params(metaobj))
(p for p in command.params() if p.name != 'version'),
(p for p in command.output_params()
if p.name not in command.params))
return result
@register() @register()
@ -568,8 +676,12 @@ class output(BaseParam):
), ),
) )
def _get_obj(self, command_output, **kwargs): @property
command, output = command_output def parent(self):
return self.api.Object.command
def _get_obj(self, cmd_output, **kwargs):
cmd, output = cmd_output
required = True required = True
multivalue = False multivalue = False
@ -577,8 +689,8 @@ class output(BaseParam):
type_type = dict type_type = dict
multivalue = isinstance(output, ListOfEntries) multivalue = isinstance(output, ListOfEntries)
elif isinstance(output, (PrimaryKey, ListOfPrimaryKeys)): elif isinstance(output, (PrimaryKey, ListOfPrimaryKeys)):
if getattr(command, 'obj', None) and command.obj.primary_key: if getattr(cmd, 'obj', None) and cmd.obj.primary_key:
type_type = command.obj.primary_key.type type_type = cmd.obj.primary_key.type
else: else:
type_type = type(None) type_type = type(None)
multivalue = isinstance(output, ListOfPrimaryKeys) multivalue = isinstance(output, ListOfPrimaryKeys)
@ -618,9 +730,9 @@ class output(BaseParam):
return obj return obj
def _retrieve(self, commandname, name, **kwargs): def _retrieve(self, commandname, name, **kwargs):
command = self.api.Command[commandname] cmd = self.api.Command[commandname]
try: try:
return (command, command.output[name]) return (cmd, cmd.output[name])
except KeyError: except KeyError:
raise errors.NotFound( raise errors.NotFound(
reason=_("%(pkey)s: %(oname)s not found") % { reason=_("%(pkey)s: %(oname)s not found") % {
@ -629,8 +741,8 @@ class output(BaseParam):
) )
def _search(self, commandname, **kwargs): def _search(self, commandname, **kwargs):
command = self.api.Command[commandname] cmd = self.api.Command[commandname]
return ((command, output) for output in command.output()) return ((cmd, output) for output in cmd.output())
@register() @register()
@ -656,11 +768,17 @@ class schema(Command):
command['output'] = list( command['output'] = list(
self.api.Object.output.search(name, **kwargs)) self.api.Object.output.search(name, **kwargs))
classes = list(self.api.Object['class'].search(**kwargs))
for cls in classes:
cls['params'] = list(
self.api.Object.param.search(cls['name'], **kwargs))
topics = list(self.api.Object.topic.search(**kwargs)) topics = list(self.api.Object.topic.search(**kwargs))
schema = dict() schema = dict()
schema['version'] = API_VERSION schema['version'] = API_VERSION
schema['commands'] = commands schema['commands'] = commands
schema['classes'] = classes
schema['topics'] = topics schema['topics'] = topics
return dict(result=schema) return dict(result=schema)