On 20.6.2016 16:13, David Kupka wrote:
On 28/04/16 14:45, Jan Cholasta wrote:
Hi,

I have pushed my thin client WIP branch to GitHub:
<https://github.com/jcholast/freeipa/tree/trac-4739>.

All commits up to "ipalib: use relative imports for cross-plugin
imports" should be good for review. The rest is subject to change
(WARNING: I will force push into this branch).

Honza


Hello!

Patches:
replica install: fix thin client regression
schema: remove `no_cli` from command schema
schema: remove redundant information
schema: merge command args and options
schema: remove output_params
schema: add object class schema
permission: handle ipapermright deprecated CLI alias on the client
passwd: handle sort order of passwd argument on the client
misc: skip `count` and `total` output in env.output_for_cli
dns: do not rely on custom param fields in record attributes
automember: add object plugin for automember_rebuild
frontend: do not crash on missing output in output_for_cli
frontend: skip `value` output in output_for_cli
frontend: don't copy command arguments to output params
makeaci, makeapi: use in-server API

work for me, ACK.

Pushed to master: 8cc8b6fb1023fa4aeafac0df01cfacff4ebf537a

Note that I haven't pushed "replica install: fix thin client regression" yet, I would like Martin to review it first.

Attaching the patches for reference.


Note: There is one one known issue in automember output, will be fixed
later.


--
Jan Cholasta
From 82c9def5f01964a64e51ebf41b3bcfb4ab6a888c Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Fri, 3 Jun 2016 09:38:11 +0200
Subject: [PATCH 01/14] makeaci, makeapi: use in-server API

Capture the server API rather than client API in API.txt. Client API may be
affected by client-side plugins and thus may not correspond to what is
transmitted over the wire.

https://fedorahosted.org/freeipa/ticket/4739
---
 API.txt | 131 +++-------------------------------------------------------------
 makeaci |   4 +-
 makeapi |   4 +-
 3 files changed, 10 insertions(+), 129 deletions(-)

diff --git a/API.txt b/API.txt
index eb14d44..35ae5e4 100644
--- a/API.txt
+++ b/API.txt
@@ -343,13 +343,6 @@ output: Output('count', type=[<type 'int'>])
 output: ListOfEntries('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: Output('truncated', type=[<type 'bool'>])
-command: automountlocation_import
-args: 2,2,1
-arg: Str('cn', cli_name='location')
-arg: Str('masterfile')
-option: Flag('continue?', autofill=True, cli_name='continue', default=False)
-option: Str('version?')
-output: Output('result')
 command: automountlocation_show
 args: 1,4,3
 arg: Str('cn', cli_name='location')
@@ -761,7 +754,7 @@ option: Str('version?')
 output: Output('result')
 command: cert_request
 args: 1,6,1
-arg: File('csr', cli_name='csr_file')
+arg: Str('csr', cli_name='csr_file')
 option: Flag('add', autofill=True, default=False)
 option: Str('cacn?', cli_name='ca')
 option: Str('principal')
@@ -816,7 +809,7 @@ args: 1,6,3
 arg: Str('cn', cli_name='id')
 option: Flag('all', autofill=True, cli_name='all', default=False)
 option: Str('description', cli_name='desc')
-option: File('file', cli_name='file')
+option: Str('file', cli_name='file')
 option: Bool('ipacertprofilestoreissued', cli_name='store', default=True)
 option: Flag('raw', autofill=True, cli_name='raw', default=False)
 option: Str('version?')
@@ -830,7 +823,7 @@ option: Str('addattr*', cli_name='addattr')
 option: Flag('all', autofill=True, cli_name='all', default=False)
 option: Str('delattr*', cli_name='delattr')
 option: Str('description?', autofill=False, cli_name='desc')
-option: File('file?', cli_name='file')
+option: Str('file?', cli_name='file')
 option: Bool('ipacertprofilestoreissued?', autofill=False, cli_name='store', default=True)
 option: Flag('raw', autofill=True, cli_name='raw', default=False)
 option: Flag('rights', autofill=True, default=False)
@@ -3018,7 +3011,7 @@ arg: Str('ldapuri', cli_name='ldap_uri')
 arg: Password('bindpw', cli_name='password', confirm=False)
 option: DNParam('basedn?', cli_name='base_dn')
 option: DNParam('binddn?', autofill=True, cli_name='bind_dn', default=ipapython.dn.DN('cn=directory manager'))
-option: File('cacertfile?', cli_name='ca_cert_file')
+option: Str('cacertfile?', cli_name='ca_cert_file')
 option: Flag('compat?', autofill=True, cli_name='with_compat', default=False)
 option: Flag('continue?', autofill=True, default=False)
 option: Str('exclude_groups*', autofill=True, cli_name='exclude_groups', default=[])
@@ -3225,25 +3218,6 @@ option: Str('version?')
 output: Output('completed', type=[<type 'int'>])
 output: Output('failed', type=[<type 'dict'>])
 output: Entry('result')
-command: otptoken_add_yubikey
-args: 1,13,3
-arg: Str('ipatokenuniqueid?', cli_name='id')
-option: Str('addattr*', cli_name='addattr')
-option: Flag('all', autofill=True, cli_name='all', default=False)
-option: Str('description?', cli_name='desc')
-option: Bool('ipatokendisabled?', cli_name='disabled')
-option: DateTime('ipatokennotafter?', cli_name='not_after')
-option: DateTime('ipatokennotbefore?', cli_name='not_before')
-option: IntEnum('ipatokenotpdigits?', autofill=True, cli_name='digits', default=6, values=[6, 8])
-option: Str('ipatokenowner?', cli_name='owner')
-option: Flag('no_members', autofill=True, default=False)
-option: Flag('raw', autofill=True, cli_name='raw', default=False)
-option: Str('setattr*', cli_name='setattr')
-option: IntEnum('slot?', cli_name='slot', values=[1, 2])
-option: Str('version?')
-output: Entry('result')
-output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-output: PrimaryKey('value')
 command: otptoken_del
 args: 1,2,3
 arg: Str('ipatokenuniqueid+', cli_name='id')
@@ -3326,15 +3300,6 @@ option: Str('version?')
 output: Entry('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: PrimaryKey('value')
-command: otptoken_sync
-args: 1,5,1
-arg: Str('token?')
-option: Password('first_code', confirm=False)
-option: Password('password', confirm=False)
-option: Password('second_code', confirm=False)
-option: Str('user')
-option: Str('version?')
-output: Output('result')
 command: output_find
 args: 2,4,4
 arg: Str('commandname', cli_name='command')
@@ -5664,11 +5629,10 @@ output: Output('completed', type=[<type 'int'>])
 output: Output('failed', type=[<type 'dict'>])
 output: Entry('result')
 command: user_del
-args: 1,4,3
+args: 1,3,3
 arg: Str('uid+', cli_name='login')
 option: Flag('continue', autofill=True, cli_name='continue', default=False)
-option: Flag('no_preserve?', autofill=True, default=False)
-option: Flag('preserve?', autofill=True, default=False)
+option: Bool('preserve?')
 option: Str('version?')
 output: Output('result', type=[<type 'dict'>])
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
@@ -5869,27 +5833,6 @@ option: Str('version?')
 output: Output('result', type=[<type 'bool'>])
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: PrimaryKey('value')
-command: vault_add
-args: 1,15,3
-arg: Str('cn', cli_name='name')
-option: Str('addattr*', cli_name='addattr')
-option: Flag('all', autofill=True, cli_name='all', default=False)
-option: Str('description?', cli_name='desc')
-option: Bytes('ipavaultpublickey?', cli_name='public_key')
-option: StrEnum('ipavaulttype?', autofill=True, cli_name='type', default=u'symmetric', values=[u'standard', u'symmetric', u'asymmetric'])
-option: Flag('no_members', autofill=True, default=False)
-option: Str('password?', cli_name='password')
-option: Str('password_file?', cli_name='password_file')
-option: Str('public_key_file?', cli_name='public_key_file')
-option: Flag('raw', autofill=True, cli_name='raw', default=False)
-option: Str('service?')
-option: Str('setattr*', cli_name='setattr')
-option: Flag('shared?', autofill=True, default=False)
-option: Str('username?', cli_name='user')
-option: Str('version?')
-output: Entry('result')
-output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-output: PrimaryKey('value')
 command: vault_add_internal
 args: 1,13,3
 arg: Str('cn', cli_name='name')
@@ -5941,23 +5884,6 @@ option: Str('version?')
 output: Output('completed', type=[<type 'int'>])
 output: Output('failed', type=[<type 'dict'>])
 output: Entry('result')
-command: vault_archive
-args: 1,11,3
-arg: Str('cn', cli_name='name')
-option: Flag('all', autofill=True, cli_name='all', default=False)
-option: Bytes('data?')
-option: Str('in?')
-option: Flag('override_password?', autofill=True, default=False)
-option: Str('password?', cli_name='password')
-option: Str('password_file?', cli_name='password_file')
-option: Flag('raw', autofill=True, cli_name='raw', default=False)
-option: Str('service?')
-option: Flag('shared?', autofill=True, default=False)
-option: Str('username?', cli_name='user')
-option: Str('version?')
-output: Entry('result')
-output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-output: PrimaryKey('value')
 command: vault_archive_internal
 args: 1,9,3
 arg: Str('cn', cli_name='name')
@@ -6006,34 +5932,6 @@ output: Output('count', type=[<type 'int'>])
 output: ListOfEntries('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: Output('truncated', type=[<type 'bool'>])
-command: vault_mod
-args: 1,22,3
-arg: Str('cn', cli_name='name')
-option: Str('addattr*', cli_name='addattr')
-option: Flag('all', autofill=True, cli_name='all', default=False)
-option: Flag('change_password?', autofill=True, default=False)
-option: Str('delattr*', cli_name='delattr')
-option: Str('description?', autofill=False, cli_name='desc')
-option: Bytes('ipavaultpublickey?', autofill=False, cli_name='public_key')
-option: StrEnum('ipavaulttype?', autofill=False, cli_name='type', default=u'symmetric', values=[u'standard', u'symmetric', u'asymmetric'])
-option: Str('new_password?', cli_name='new_password')
-option: Str('new_password_file?', cli_name='new_password_file')
-option: Flag('no_members', autofill=True, default=False)
-option: Str('old_password?', cli_name='old_password')
-option: Str('old_password_file?', cli_name='old_password_file')
-option: Bytes('private_key?', cli_name='private_key')
-option: Str('private_key_file?', cli_name='private_key_file')
-option: Str('public_key_file?', cli_name='public_key_file')
-option: Flag('raw', autofill=True, cli_name='raw', default=False)
-option: Flag('rights', autofill=True, default=False)
-option: Str('service?')
-option: Str('setattr*', cli_name='setattr')
-option: Flag('shared?', autofill=True, default=False)
-option: Str('username?', cli_name='user')
-option: Str('version?')
-output: Entry('result')
-output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-output: PrimaryKey('value')
 command: vault_mod_internal
 args: 1,15,3
 arg: Str('cn', cli_name='name')
@@ -6087,23 +5985,6 @@ option: Str('version?')
 output: Output('completed', type=[<type 'int'>])
 output: Output('failed', type=[<type 'dict'>])
 output: Entry('result')
-command: vault_retrieve
-args: 1,11,3
-arg: Str('cn', cli_name='name')
-option: Flag('all', autofill=True, cli_name='all', default=False)
-option: Str('out?')
-option: Str('password?', cli_name='password')
-option: Str('password_file?', cli_name='password_file')
-option: Bytes('private_key?', cli_name='private_key')
-option: Str('private_key_file?', cli_name='private_key_file')
-option: Flag('raw', autofill=True, cli_name='raw', default=False)
-option: Str('service?')
-option: Flag('shared?', autofill=True, default=False)
-option: Str('username?', cli_name='user')
-option: Str('version?')
-output: Entry('result')
-output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
-output: PrimaryKey('value')
 command: vault_retrieve_internal
 args: 1,7,3
 arg: Str('cn', cli_name='name')
diff --git a/makeaci b/makeaci
index ea56837..3cce76d 100755
--- a/makeaci
+++ b/makeaci
@@ -86,8 +86,8 @@ def check_member_attrs(name, template):
 
 def main(options):
     api.bootstrap(
-        context='cli',
-        in_server=False,
+        context='server',
+        in_server=True,
         in_tree=True,
         debug=False,
         verbose=0,
diff --git a/makeapi b/makeapi
index b954ef5..8e66eec 100755
--- a/makeapi
+++ b/makeapi
@@ -456,8 +456,8 @@ def main():
     options, args = parse_options()
 
     cfg = dict(
-        context='cli',
-        in_server=False,
+        context='server',
+        in_server=True,
         in_tree=True,
         debug=False,
         verbose=0,
-- 
2.7.4

From da4c848a2dc4496319a581f3fe78cfbc3accf76d Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Jun 2016 16:00:49 +0200
Subject: [PATCH 02/14] frontend: don't copy command arguments to output params

Use only object params and params defined in has_output_params as output
params. This removes unnecessary duplication of params defined both in
object plugins and as command arguments.

This requires all command output params to be properly defined in either
the object plugins or the command's has_output_params. Fix the plugins
where this wasn't true.

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaclient/plugins/otptoken_yubikey.py |  1 +
 ipalib/frontend.py                    | 13 +------------
 ipaserver/plugins/aci.py              |  2 ++
 ipaserver/plugins/automember.py       | 14 +++++++++-----
 ipaserver/plugins/cert.py             |  5 +++++
 ipaserver/plugins/group.py            | 19 ++++++++++---------
 ipaserver/plugins/permission.py       | 13 ++++++++++---
 ipaserver/plugins/schema.py           |  3 +--
 ipatests/test_ipalib/test_frontend.py |  6 +++---
 9 files changed, 42 insertions(+), 34 deletions(-)

diff --git a/ipaclient/plugins/otptoken_yubikey.py b/ipaclient/plugins/otptoken_yubikey.py
index c1ca88e..e9aaba9 100644
--- a/ipaclient/plugins/otptoken_yubikey.py
+++ b/ipaclient/plugins/otptoken_yubikey.py
@@ -61,6 +61,7 @@ class otptoken_add_yubikey(Command):
             values=(1, 2),
         ),
     )
+    has_output_params = takes_options
 
     def get_args(self):
         for arg in self.api.Command.otptoken_add.args():
diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index 49ff8a1..672f557 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -991,12 +991,6 @@ class Command(HasParam):
     def get_output_params(self):
         for param in self._get_param_iterable('output_params', verb='has'):
             yield param
-        if self.params is None:
-            return
-        for param in self.params():
-            if 'no_output' in param.flags:
-                continue
-            yield param
 
     def get_summary_default(self, output):
         if self.msg_summary:
@@ -1421,12 +1415,7 @@ class Method(Attribute, Command):
             if 'no_output' in param.flags:
                 continue
             yield param
-        for param in self.params():
-            if param.name not in list(self.obj.params):
-                if 'no_output' in param.flags:
-                    continue
-                yield param
-        for param in self._get_param_iterable('output_params', verb='has'):
+        for param in super(Method, self).get_output_params():
             yield param
 
 
diff --git a/ipaserver/plugins/aci.py b/ipaserver/plugins/aci.py
index 01c9292..dd14d82 100644
--- a/ipaserver/plugins/aci.py
+++ b/ipaserver/plugins/aci.py
@@ -423,6 +423,7 @@ _prefix_option = StrEnum('aciprefix',
                 doc=_('Prefix used to distinguish ACI types ' \
                     '(permission, delegation, selfservice, none)'),
                 values=_valid_prefix_values,
+                flags={'no_create', 'no_update', 'no_search'},
                 )
 
 
@@ -505,6 +506,7 @@ class aci(Object):
              doc=_('Apply ACI to your own entry (self)'),
              flags=('virtual_attribute',),
         ),
+        _prefix_option,
     )
 
 
diff --git a/ipaserver/plugins/automember.py b/ipaserver/plugins/automember.py
index 89b9dfa..0a7f351 100644
--- a/ipaserver/plugins/automember.py
+++ b/ipaserver/plugins/automember.py
@@ -155,13 +155,18 @@ regex_attrs = (
         label=_('Inclusive Regex'),
         doc=_('Inclusive Regex'),
         alwaysask=True,
+        flags={'no_create', 'no_update', 'no_search'},
     ),
     Str('automemberexclusiveregex*',
         cli_name='exclusive_regex',
         label=_('Exclusive Regex'),
         doc=_('Exclusive Regex'),
         alwaysask=True,
+        flags={'no_create', 'no_update', 'no_search'},
     ),
+)
+
+regex_key = (
     Str('key',
         label=_('Attribute Key'),
         doc=_('Attribute to filter via regex. For example fqdn for a host, or manager for a user'),
@@ -183,6 +188,7 @@ automember_rule = (
         label=_('Automember Rule'),
         doc=_('Automember Rule'),
         normalizer=lambda value: value.lower(),
+        flags={'no_create', 'no_update', 'no_search'},
     ),
 )
 
@@ -254,7 +260,7 @@ class automember(LDAPObject):
             doc=_('Default group for entries to land'),
             flags=['no_create', 'no_update', 'no_search']
         ),
-    )
+    ) + automember_rule + regex_attrs
 
     def dn_exists(self, otype, oname):
         ldap = self.api.Backend.ldap2
@@ -336,7 +342,7 @@ class automember_add_condition(LDAPUpdate):
         ),
     )
 
-    takes_options = regex_attrs + group_type
+    takes_options = regex_attrs + regex_key + group_type
     takes_args = automember_rule
     msg_summary = _('Added condition(s) to "%(value)s"')
 
@@ -421,7 +427,7 @@ class automember_remove_condition(LDAPUpdate):
     __doc__ = _("""
     Remove conditions from an automember rule.
     """)
-    takes_options = regex_attrs + group_type
+    takes_options = regex_attrs + regex_key + group_type
     takes_args = automember_rule
     msg_summary = _('Removed condition(s) from "%(value)s"')
 
@@ -539,7 +545,6 @@ class automember_find(LDAPSearch):
     Search for automember rules.
     """)
     takes_options = group_type
-    has_output_params = LDAPSearch.has_output_params + automember_rule + regex_attrs
 
     msg_summary = ngettext(
         '%(count)d rules matched', '%(count)d rules matched', 0
@@ -559,7 +564,6 @@ class automember_show(LDAPRetrieve):
     """)
     takes_args = automember_rule
     takes_options = group_type
-    has_output_params = LDAPRetrieve.has_output_params + regex_attrs
 
     def execute(self, *keys, **options):
         result = super(automember_show, self).execute(*keys, **options)
diff --git a/ipaserver/plugins/cert.py b/ipaserver/plugins/cert.py
index 9926c1f..76a2fbc 100644
--- a/ipaserver/plugins/cert.py
+++ b/ipaserver/plugins/cert.py
@@ -553,6 +553,7 @@ class cert_status(VirtualCommand):
         Str('cert_request_status',
             label=_('Request status'),
         ),
+        takes_args[0],
     )
     operation = "certificate status"
 
@@ -607,6 +608,7 @@ class cert_show(VirtualCommand):
         Str('serial_number_hex',
             label=_('Serial number (hex)'),
         ),
+        _serial_number,
     )
 
     takes_options = (
@@ -845,6 +847,9 @@ class cert_find(Command):
         Str('status',
             label=_('Status'),
         ),
+        Str('subject',
+            label=_('Subject'),
+        ),
     )
 
     msg_summary = ngettext(
diff --git a/ipaserver/plugins/group.py b/ipaserver/plugins/group.py
index 2b0c080..dcd4a91 100644
--- a/ipaserver/plugins/group.py
+++ b/ipaserver/plugins/group.py
@@ -139,6 +139,14 @@ register = Registry()
 PROTECTED_GROUPS = (u'admins', u'trust admins', u'default smb group')
 
 
+ipaexternalmember_param = Str('ipaexternalmember*',
+            cli_name='external',
+            label=_('External member'),
+            doc=_('Members of a trusted domain in DOM\\name or name@domain form'),
+            flags=['no_create', 'no_update', 'no_search'],
+        )
+
+
 @register()
 class group(LDAPObject):
     """
@@ -271,17 +279,10 @@ class group(LDAPObject):
             doc=_('GID (use this option to set it manually)'),
             minvalue=1,
         ),
+        ipaexternalmember_param,
     )
 
 
-ipaexternalmember_param = Str('ipaexternalmember*',
-            cli_name='external',
-            label=_('External member'),
-            doc=_('Members of a trusted domain in DOM\\name or name@domain form'),
-            flags=['no_create', 'no_update', 'no_search'],
-        )
-
-
 @register()
 class group_add(LDAPCreate):
     __doc__ = _('Create a new group.')
@@ -498,7 +499,7 @@ class group_find(LDAPSearch):
 @register()
 class group_show(LDAPRetrieve):
     __doc__ = _('Display information about a named group.')
-    has_output_params = LDAPRetrieve.has_output_params + (ipaexternalmember_param,)
+
     def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
         assert isinstance(dn, DN)
         if ('ipaexternalmember' in entry_attrs and
diff --git a/ipaserver/plugins/permission.py b/ipaserver/plugins/permission.py
index 9f19358..9711727 100644
--- a/ipaserver/plugins/permission.py
+++ b/ipaserver/plugins/permission.py
@@ -165,6 +165,13 @@ def _disallow_colon(option):
     )
 
 
+_ipapermissiontype_param = Str(
+    'ipapermissiontype+',
+    label=_('Permission flags'),
+    flags={'no_create', 'no_update', 'no_search'},
+)
+
+
 @register()
 class permission(baseldap.LDAPObject):
     """
@@ -346,6 +353,8 @@ class permission(baseldap.LDAPObject):
             doc=_('Deprecated; use %s' % new_name),
             flags={'no_option', 'virtual_attribute'})
         for old_name, new_name in _DEPRECATED_OPTION_ALIASES.items()
+    ) + (
+        _ipapermissiontype_param,
     )
 
     def reject_system(self, entry):
@@ -945,9 +954,7 @@ class permission_add_noaci(baseldap.LDAPCreate):
     has_output_params = baseldap.LDAPCreate.has_output_params + output_params
 
     takes_options = (
-        Str('ipapermissiontype+',
-            label=_('Permission flags'),
-        ),
+        _ipapermissiontype_param,
     )
 
     def get_options(self):
diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index e5aac6f..cbdb70d 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -181,8 +181,7 @@ class command(MetaObject):
 
         if len(command.output_params):
             obj['output_params_param'] = tuple(
-                unicode(n) for n in command.output_params
-                if n not in command.params)
+                unicode(n) for n in command.output_params)
 
         return obj
 
diff --git a/ipatests/test_ipalib/test_frontend.py b/ipatests/test_ipalib/test_frontend.py
index 518dadc..93ab547 100644
--- a/ipatests/test_ipalib/test_frontend.py
+++ b/ipatests/test_ipalib/test_frontend.py
@@ -814,16 +814,16 @@ class test_Command(ClassChecker):
                 'foo',
             )
             takes_options = (
-                Str('bar', flags='no_output'),
+                Str('bar'),
                 'baz',
             )
 
         inst = example(api)
         inst.finalize()
         assert list(inst.get_output_params()) == [
-            'one', 'two', 'three', inst.params.foo, inst.params.baz
+            'one', 'two', 'three'
         ]
-        assert list(inst.output_params) == ['one', 'two', 'three', 'foo', 'baz']
+        assert list(inst.output_params) == ['one', 'two', 'three']
 
 
 class test_LocalOrRemote(ClassChecker):
-- 
2.7.4

From c29d4dc450cbf11d2e762107c42397e2330ededc Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 20 Jun 2016 12:26:10 +0200
Subject: [PATCH 03/14] frontend: skip `value` output in output_for_cli

Do not include the `value` output value in CLI output in
Command.output_for_cli by default.

https://fedorahosted.org/freeipa/ticket/4739
---
 ipalib/frontend.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index 672f557..d483c11 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -1050,7 +1050,9 @@ class Command(HasParam):
                 continue
             result = output[o]
 
-            if o.lower() == 'count' and result == 0:
+            if o == 'value':
+                continue
+            elif o.lower() == 'count' and result == 0:
                 rv = 1
             elif o.lower() == 'failed':
                 if entry_count(result) == 0:
-- 
2.7.4

From 15dab762eae5d958ace2ba007dd924814a4d6caf Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 20 Jun 2016 12:29:23 +0200
Subject: [PATCH 04/14] frontend: do not crash on missing output in
 output_for_cli

Do not crash in Command.output_for_cli when an output value is missing.

https://fedorahosted.org/freeipa/ticket/4739
---
 ipalib/frontend.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ipalib/frontend.py b/ipalib/frontend.py
index d483c11..3edd298 100644
--- a/ipalib/frontend.py
+++ b/ipalib/frontend.py
@@ -1048,7 +1048,7 @@ class Command(HasParam):
             outp = self.output[o]
             if 'no_display' in outp.flags:
                 continue
-            result = output[o]
+            result = output.get(o)
 
             if o == 'value':
                 continue
-- 
2.7.4

From b8eb0a79aee032aa9d0f4700d3b3870b476758d3 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 16 Jun 2016 10:01:57 +0200
Subject: [PATCH 05/14] automember: add object plugin for automember_rebuild

Change automember_rebuild into a method of a new automember_task object.

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaserver/plugins/automember.py | 26 +++++++++++++++++---------
 1 file changed, 17 insertions(+), 9 deletions(-)

diff --git a/ipaserver/plugins/automember.py b/ipaserver/plugins/automember.py
index 0a7f351..26fef04 100644
--- a/ipaserver/plugins/automember.py
+++ b/ipaserver/plugins/automember.py
@@ -23,7 +23,7 @@ import ldap as _ldap
 import six
 
 from ipalib import api, errors, Str, StrEnum, DNParam, Flag, _, ngettext
-from ipalib import output, Command
+from ipalib import output, Method, Object
 from ipalib.plugable import Registry
 from .baseldap import (
     pkey_to_value,
@@ -658,8 +658,23 @@ class automember_default_group_show(LDAPRetrieve):
 
 
 @register()
-class automember_rebuild(Command):
+class automember_task(Object):
+    takes_params = (
+        DNParam(
+            'dn',
+            label=_('Task DN'),
+            doc=_('DN of the started task'),
+        ),
+    )
+
+
+@register()
+class automember_rebuild(Method):
     __doc__ = _('Rebuild auto membership.')
+
+    obj_name = 'automember_task'
+    attr_name = 'rebuild'
+
     # TODO: Add a --dry-run option:
     # https://fedorahosted.org/freeipa/ticket/3936
     takes_options = (
@@ -685,13 +700,6 @@ class automember_rebuild(Command):
         ),
     )
     has_output = output.standard_entry
-    has_output_params = (
-        DNParam(
-            'dn',
-            label=_('Task DN'),
-            doc=_('DN of the started task'),
-        ),
-    )
 
     def validate(self, **kw):
         """
-- 
2.7.4

From 60c56126d0bb1ba433368d9564e40d6529c81796 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 28 Apr 2016 10:32:00 +0200
Subject: [PATCH 06/14] dns: do not rely on custom param fields in record
 attributes

Obtain the information provided by the `hint` kwarg and `dnsrecord_part`
and `dnsrecord_extra` flags by other means.

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaclient/plugins/dns.py |  7 ++++---
 ipalib/dns.py            | 32 +++++++++++++++++++++++++-------
 ipaserver/plugins/dns.py | 36 +++++++++++++++++-------------------
 3 files changed, 46 insertions(+), 29 deletions(-)

diff --git a/ipaclient/plugins/dns.py b/ipaclient/plugins/dns.py
index 06cd2c2..d04f686 100644
--- a/ipaclient/plugins/dns.py
+++ b/ipaclient/plugins/dns.py
@@ -25,7 +25,8 @@ import copy
 
 from ipaclient.frontend import MethodOverride, CommandOverride
 from ipalib import errors
-from ipalib.dns import (get_record_rrtype,
+from ipalib.dns import (get_part_rrtype,
+                        get_record_rrtype,
                         has_cli_options,
                         iterate_rrparams_by_parts,
                         record_name_format)
@@ -63,7 +64,7 @@ def prompt_parts(rrtype, cmd, mod_dnsvalue=None):
             name, mod_dnsvalue)['result']
 
     user_options = {}
-    parts = [p for p in cmd.params() if 'dnsrecord_part' in p.flags]
+    parts = [p for p in cmd.params() if get_part_rrtype(p.name) == rrtype]
     if not parts:
         return user_options
 
@@ -80,7 +81,7 @@ def prompt_parts(rrtype, cmd, mod_dnsvalue=None):
 
 def prompt_missing_parts(rrtype, cmd, kw, prompt_optional=False):
     user_options = {}
-    parts = [p for p in cmd.params() if 'dnsrecord_part' in p.flags]
+    parts = [p for p in cmd.params() if get_part_rrtype(p.name) == rrtype]
     if not parts:
         return user_options
 
diff --git a/ipalib/dns.py b/ipalib/dns.py
index 55e45a0..95c7989 100644
--- a/ipalib/dns.py
+++ b/ipalib/dns.py
@@ -26,6 +26,8 @@ from ipalib import errors
 
 # dnsrecord param name formats
 record_name_format = '%srecord'
+part_name_format = "%s_part_%s"
+extra_name_format = "%s_extra_%s"
 
 
 def get_record_rrtype(name):
@@ -36,6 +38,22 @@ def get_record_rrtype(name):
     return match.group(1).upper()
 
 
+def get_part_rrtype(name):
+    match = re.match('([^_]+)_part_.*$', name)
+    if match is None:
+        return None
+
+    return match.group(1).upper()
+
+
+def get_extra_rrtype(name):
+    match = re.match('([^_]+)_extra_.*$', name)
+    if match is None:
+        return None
+
+    return match.group(1).upper()
+
+
 def has_cli_options(cmd, options, no_option_msg, allow_empty_attrs=False):
     sufficient = ('setattr', 'addattr', 'delattr', 'rename')
     if any(k in options for k in sufficient):
@@ -43,9 +61,8 @@ def has_cli_options(cmd, options, no_option_msg, allow_empty_attrs=False):
 
     has_options = False
     for attr in options.keys():
-        obj_params = [
-            p.name for p in cmd.params()
-            if get_record_rrtype(p.name) or 'dnsrecord_part' in p.flags]
+        obj_params = [n for n in cmd.params
+                      if get_record_rrtype(n) or get_part_rrtype(n)]
         if attr in obj_params:
             if options[attr] or allow_empty_attrs:
                 has_options = True
@@ -65,13 +82,14 @@ def get_rrparam_from_part(cmd, part_name):
     try:
         param = cmd.params[part_name]
 
-        if not any(flag in param.flags for flag in
-                   ('dnsrecord_part', 'dnsrecord_extra')):
+        rrtype = (get_part_rrtype(param.name) or
+                  get_extra_rrtype(param.name))
+        if not rrtype:
             return None
 
         # All DNS record part or extra parameters contain a name of its
         # parent RR parameter in its hint attribute
-        rrparam = cmd.params[param.hint]
+        rrparam = cmd.params[record_name_format % rrtype.lower()]
     except (KeyError, AttributeError):
         return None
 
@@ -94,7 +112,7 @@ def iterate_rrparams_by_parts(cmd, kw, skip_extra=False):
         if rrparam is None:
             continue
 
-        if skip_extra and 'dnsrecord_extra' in cmd.params[opt].flags:
+        if skip_extra and get_extra_rrtype(opt):
             continue
 
         if rrparam.name not in processed:
diff --git a/ipaserver/plugins/dns.py b/ipaserver/plugins/dns.py
index 094f8eb..6e56dc8 100644
--- a/ipaserver/plugins/dns.py
+++ b/ipaserver/plugins/dns.py
@@ -32,10 +32,14 @@ import dns.rdatatype
 import dns.resolver
 import six
 
-from ipalib.dns import (get_record_rrtype,
+from ipalib.dns import (extra_name_format,
+                        get_extra_rrtype,
+                        get_part_rrtype,
+                        get_record_rrtype,
                         get_rrparam_from_part,
                         has_cli_options,
                         iterate_rrparams_by_parts,
+                        part_name_format,
                         record_name_format)
 from ipalib.request import context
 from ipalib import api, errors, output
@@ -657,8 +661,6 @@ class DNSRecord(Str):
     doc_format = _('Raw %s records')
     option_group_format = _('%s Record')
     see_rfc_msg = _("(see RFC %s for details)")
-    part_name_format = "%s_part_%s"
-    extra_name_format = "%s_extra_%s"
     cli_name_format = "%s_%s"
     format_error_msg = None
 
@@ -708,8 +710,8 @@ class DNSRecord(Str):
         return u" ".join(parts)
 
     def get_parts_from_kw(self, kw, raise_on_none=True):
-        part_names = tuple(self.part_name_format % (self.rrtype.lower(), part.name) \
-                               for part in self.parts)
+        part_names = tuple(part_name_format % (self.rrtype.lower(), part.name)
+                           for part in self.parts)
         vals = tuple(kw.get(part_name) for part_name in part_names)
 
         if all(val is None for val in vals):
@@ -813,11 +815,11 @@ class DNSRecord(Str):
         can be added to global DNS API. For example a prefix need to be added
         before part name so that the name is unique in the global namespace.
         """
-        name = self.part_name_format % (self.rrtype.lower(), part.name)
+        name = part_name_format % (self.rrtype.lower(), part.name)
         cli_name = self.cli_name_format % (self.rrtype.lower(), part.name)
         label = self.part_label_format % (self.rrtype, unicode(part.label))
         option_group = self.option_group_format % self.rrtype
-        flags = list(part.flags) + ['dnsrecord_part', 'virtual_attribute',]
+        flags = list(part.flags) + ['virtual_attribute']
         if not part.required:
             flags.append('dnsrecord_optional')
         if not self.supported:
@@ -828,27 +830,25 @@ class DNSRecord(Str):
                      label=label,
                      required=False,
                      option_group=option_group,
-                     flags=flags,
-                     hint=self.name,)   # name of parent RR param
+                     flags=flags)
 
     def _convert_dnsrecord_extra(self, extra):
         """
         Parameters for special per-type behavior need to be processed in the
         same way as record parts in _convert_dnsrecord_part().
         """
-        name = self.extra_name_format % (self.rrtype.lower(), extra.name)
+        name = extra_name_format % (self.rrtype.lower(), extra.name)
         cli_name = self.cli_name_format % (self.rrtype.lower(), extra.name)
         label = self.part_label_format % (self.rrtype, unicode(extra.label))
         option_group = self.option_group_format % self.rrtype
-        flags = list(extra.flags) + ['dnsrecord_extra', 'virtual_attribute',]
+        flags = list(extra.flags) + ['virtual_attribute']
 
         return extra.clone_rename(name,
                      cli_name=cli_name,
                      label=label,
                      required=False,
                      option_group=option_group,
-                     flags=flags,
-                     hint=self.name,)   # name of parent RR param
+                     flags=flags)
 
     def get_parts(self):
         if self.parts is None:
@@ -3509,7 +3509,7 @@ class dnsrecord_add(LDAPCreate):
             if rrparam is None:
                 continue
 
-            if 'dnsrecord_part' in param.flags:
+            if get_part_rrtype(param.name):
                 if rrparam.name in processed_attrs:
                     # this record was already entered
                     continue
@@ -3525,7 +3525,7 @@ class dnsrecord_add(LDAPCreate):
                 processed_attrs.append(rrparam.name)
                 continue
 
-            if 'dnsrecord_extra' in param.flags:
+            if get_extra_rrtype(param.name):
                 # do not run precallback for unset flags
                 if isinstance(param, Flag) and not options[option]:
                     continue
@@ -3779,8 +3779,7 @@ class dnsrecord_del(LDAPUpdate):
 
     def get_options(self):
         for option in super(dnsrecord_del, self).get_options():
-            if any(flag in option.flags for flag in \
-                    ('dnsrecord_part', 'dnsrecord_extra',)):
+            if get_part_rrtype(option.name) or get_extra_rrtype(option.name):
                 continue
             elif option.name in ('rename', ):
                 # options only valid for dnsrecord-mod
@@ -3910,8 +3909,7 @@ class dnsrecord_find(LDAPSearch):
 
     def get_options(self):
         for option in super(dnsrecord_find, self).get_options():
-            if any(flag in option.flags for flag in \
-                    ('dnsrecord_part', 'dnsrecord_extra',)):
+            if get_part_rrtype(option.name) or get_extra_rrtype(option.name):
                 continue
             elif isinstance(option, DNSRecord):
                 yield option.clone(option_group=None)
-- 
2.7.4

From 084b7a9fa63574a1c8ba9e5586db11e062626595 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 20 Jun 2016 12:32:54 +0200
Subject: [PATCH 07/14] misc: skip `count` and `total` output in
 env.output_for_cli

Do not include the `count` and `total` output values in CLI output in
env.output_for_cli by default.

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaclient/plugins/misc.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/ipaclient/plugins/misc.py b/ipaclient/plugins/misc.py
index f3d6326..05fc542 100644
--- a/ipaclient/plugins/misc.py
+++ b/ipaclient/plugins/misc.py
@@ -11,6 +11,9 @@ register = Registry()
 @register(override=True)
 class env(CommandOverride):
     def output_for_cli(self, textui, output, *args, **options):
+        output = dict(output)
+        output.pop('count', None)
+        output.pop('total', None)
         options['all'] = True
         return super(env, self).output_for_cli(textui, output,
                                                *args, **options)
-- 
2.7.4

From 070f7cc092cb9831078dab9dfeea098bd431e73c Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 20 Jun 2016 12:40:36 +0200
Subject: [PATCH 08/14] passwd: handle sort order of passwd argument on the
 client

https://fedorahosted.org/freeipa/ticket/4739
---
 ipaclient/plugins/passwd.py | 17 +++++++++++++++++
 ipaserver/plugins/passwd.py |  1 -
 2 files changed, 17 insertions(+), 1 deletion(-)
 create mode 100644 ipaclient/plugins/passwd.py

diff --git a/ipaclient/plugins/passwd.py b/ipaclient/plugins/passwd.py
new file mode 100644
index 0000000..7382306
--- /dev/null
+++ b/ipaclient/plugins/passwd.py
@@ -0,0 +1,17 @@
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+
+from ipaclient.frontend import CommandOverride
+from ipalib.plugable import Registry
+
+register = Registry()
+
+
+@register(override=True)
+class passwd(CommandOverride):
+    def get_args(self):
+        for arg in super(passwd, self).get_args():
+            if arg.name == 'current_password':
+                arg = arg.clone(sortorder=-1)
+            yield arg
diff --git a/ipaserver/plugins/passwd.py b/ipaserver/plugins/passwd.py
index 8f6b80d..253a0d3 100644
--- a/ipaserver/plugins/passwd.py
+++ b/ipaserver/plugins/passwd.py
@@ -85,7 +85,6 @@ class passwd(Command):
                  confirm=False,
                  default_from=lambda principal: get_current_password(principal),
                  autofill=True,
-                 sortorder=-1,
         ),
     )
 
-- 
2.7.4

From 2c3df5d79762a73bea227b9af8ed5a8fe49d64f5 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 20 Jun 2016 12:37:48 +0200
Subject: [PATCH 09/14] permission: handle ipapermright deprecated CLI alias on
 the client

https://fedorahosted.org/freeipa/ticket/4739
---
 API.txt                         |  6 +++---
 ipaclient/plugins/permission.py | 31 +++++++++++++++++++++++++++++++
 ipaserver/plugins/permission.py |  1 -
 3 files changed, 34 insertions(+), 4 deletions(-)
 create mode 100644 ipaclient/plugins/permission.py

diff --git a/API.txt b/API.txt
index 35ae5e4..613bf35 100644
--- a/API.txt
+++ b/API.txt
@@ -3364,7 +3364,7 @@ option: Str('extratargetfilter*', cli_name='filter')
 option: Str('filter*')
 option: StrEnum('ipapermbindruletype', autofill=True, cli_name='bindtype', default=u'permission', values=[u'permission', u'all', u'anonymous'])
 option: DNOrURL('ipapermlocation?', alwaysask=True, autofill=False, cli_name='subtree')
-option: StrEnum('ipapermright*', alwaysask=True, autofill=False, cli_name='right', deprecated_cli_aliases=['permissions'], values=[u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'])
+option: StrEnum('ipapermright*', alwaysask=True, autofill=False, cli_name='right', values=[u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'])
 option: DNParam('ipapermtarget?', cli_name='target')
 option: Str('ipapermtargetfilter*', cli_name='rawfilter')
 option: DNParam('ipapermtargetfrom?', cli_name='targetfrom')
@@ -3425,7 +3425,7 @@ option: Str('ipapermdefaultattr*', autofill=False, cli_name='defaultattrs')
 option: Str('ipapermexcludedattr*', autofill=False, cli_name='excludedattrs')
 option: Str('ipapermincludedattr*', autofill=False, cli_name='includedattrs')
 option: DNOrURL('ipapermlocation?', autofill=False, cli_name='subtree')
-option: StrEnum('ipapermright*', autofill=False, cli_name='right', deprecated_cli_aliases=['permissions'], values=[u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'])
+option: StrEnum('ipapermright*', autofill=False, cli_name='right', values=[u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'])
 option: DNParam('ipapermtarget?', autofill=False, cli_name='target')
 option: Str('ipapermtargetfilter*', autofill=False, cli_name='rawfilter')
 option: DNParam('ipapermtargetfrom?', autofill=False, cli_name='targetfrom')
@@ -3458,7 +3458,7 @@ option: StrEnum('ipapermbindruletype?', autofill=False, cli_name='bindtype', def
 option: Str('ipapermexcludedattr*', autofill=False, cli_name='excludedattrs')
 option: Str('ipapermincludedattr*', autofill=False, cli_name='includedattrs')
 option: DNOrURL('ipapermlocation?', autofill=False, cli_name='subtree')
-option: StrEnum('ipapermright*', autofill=False, cli_name='right', deprecated_cli_aliases=['permissions'], values=[u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'])
+option: StrEnum('ipapermright*', autofill=False, cli_name='right', values=[u'read', u'search', u'compare', u'write', u'add', u'delete', u'all'])
 option: DNParam('ipapermtarget?', autofill=False, cli_name='target')
 option: Str('ipapermtargetfilter*', autofill=False, cli_name='rawfilter')
 option: DNParam('ipapermtargetfrom?', autofill=False, cli_name='targetfrom')
diff --git a/ipaclient/plugins/permission.py b/ipaclient/plugins/permission.py
new file mode 100644
index 0000000..2ec1eb4
--- /dev/null
+++ b/ipaclient/plugins/permission.py
@@ -0,0 +1,31 @@
+#
+# Copyright (C) 2016  FreeIPA Contributors see COPYING for license
+#
+
+from ipaclient.frontend import MethodOverride
+from ipalib.plugable import Registry
+
+register = Registry()
+
+
+class PermissionMethodOverride(MethodOverride):
+    def get_options(self):
+        for option in super(PermissionMethodOverride, self).get_options():
+            if option.name == 'ipapermright':
+                option = option.clone(deprecated_cli_aliases={'permissions'})
+            yield option
+
+
+@register(override=True)
+class permission_add(PermissionMethodOverride):
+    pass
+
+
+@register(override=True)
+class permission_mod(PermissionMethodOverride):
+    pass
+
+
+@register(override=True)
+class permission_find(PermissionMethodOverride):
+    pass
diff --git a/ipaserver/plugins/permission.py b/ipaserver/plugins/permission.py
index 9711727..801e7fa 100644
--- a/ipaserver/plugins/permission.py
+++ b/ipaserver/plugins/permission.py
@@ -243,7 +243,6 @@ class permission(baseldap.LDAPObject):
         StrEnum(
             'ipapermright*',
             cli_name='right',
-            deprecated_cli_aliases={'permissions'},
             label=_('Granted rights'),
             doc=_('Rights to grant '
                   '(read, search, compare, write, add, delete, all)'),
-- 
2.7.4

From 8d3fd36b56ab66477ff3949503b06ac795af91a9 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 16 Jun 2016 13:21:17 +0200
Subject: [PATCH 10/14] 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
---
 API.txt                            |  24 +++-
 VERSION                            |   4 +-
 ipaclient/remote_plugins/schema.py |  52 +++++--
 ipaserver/plugins/schema.py        | 284 ++++++++++++++++++++++++++-----------
 4 files changed, 267 insertions(+), 97 deletions(-)

diff --git a/API.txt b/API.txt
index 613bf35..f2a0686 100644
--- a/API.txt
+++ b/API.txt
@@ -843,6 +843,26 @@ option: Str('version?')
 output: Entry('result')
 output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 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
 args: 1,3,1
 arg: Str('name')
@@ -3324,7 +3344,7 @@ output: Output('summary', type=[<type 'unicode'>, <type 'NoneType'>])
 output: PrimaryKey('value')
 command: param_find
 args: 2,4,4
-arg: Str('commandname', cli_name='command')
+arg: Str('metaobjectname', cli_name='metaobject')
 arg: Str('criteria?')
 option: Flag('all', autofill=True, cli_name='all', 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'>])
 command: param_show
 args: 2,3,3
-arg: Str('commandname', cli_name='command')
+arg: Str('metaobjectname', cli_name='metaobject')
 arg: Str('name')
 option: Flag('all', autofill=True, cli_name='all', default=False)
 option: Flag('raw', autofill=True, cli_name='raw', default=False)
diff --git a/VERSION b/VERSION
index 16e501d..a603cbe 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=188
-# Last change: mbabinsk - extend server-del to perform full master removal
+IPA_API_VERSION_MINOR=189
+# Last change: schema: add object class schema
diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py
index 28c3be7..d026d44 100644
--- a/ipaclient/remote_plugins/schema.py
+++ b/ipaclient/remote_plugins/schema.py
@@ -10,8 +10,8 @@ import types
 import six
 
 from ipaclient.plugins.rpcclient import rpcclient
-from ipalib import Command
 from ipalib import parameters, plugable
+from ipalib.frontend import Command, Object
 from ipalib.output import Output
 from ipalib.parameters import Bool, DefaultFrom, Flag, Password, Str
 from ipalib.text import ConcatenatedLazyText
@@ -226,8 +226,23 @@ def _create_command(schema):
     return command
 
 
-class _LazySchemaCommand(object):
-    def __init__(self, schema):
+def _create_class(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.__class = None
         self.__module__ = None
@@ -236,21 +251,32 @@ class _LazySchemaCommand(object):
     def name(self):
         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):
         if self.__class is None:
-            command = _create_command(self.__schema)
-            name = command.pop('name')
-            command = type(name, (_SchemaCommand,), command)
-            command.__module__ = self.__module__
-            self.__class = command
+            if self.__base is Command:
+                metaobject = _create_command(self.__schema)
+            else:
+                metaobject = _create_class(self.__schema)
+            metaobject = type(self.name, self.bases, metaobject)
+            metaobject.__module__ = self.__module__
+            self.__class = metaobject
 
         return self.__class(api)
 
 
 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):
@@ -289,6 +315,7 @@ def get_package(api):
         client.disconnect()
 
     commands = _create_commands(schema['commands'])
+    classes = _create_classes(schema['classes'])
     topics = _create_topics(schema['topics'])
 
     package = types.ModuleType(package_name)
@@ -308,6 +335,11 @@ def get_package(api):
         command = module.register()(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:
         name = topic.pop('name')
         module_name = '.'.join((package_name, name))
diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index cbdb70d..ca662a6 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -8,6 +8,7 @@ import sys
 
 import six
 
+from .baseldap import LDAPObject
 from ipalib import errors
 from ipalib.crud import PKQuery, Retrieve, Search
 from ipalib.frontend import Command, Local, Method, Object
@@ -130,8 +131,41 @@ class MetaSearch(BaseMetaSearch):
 
 
 @register()
-class command(MetaObject):
-    takes_params = BaseMetaObject.takes_params + (
+class metaobject(MetaObject):
+    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(
             'args_param*',
             label=_("Arguments"),
@@ -154,42 +188,49 @@ class command(MetaObject):
         ),
     )
 
-    def _get_obj(self, command, **kwargs):
-        obj = dict()
-        obj['name'] = unicode(command.name)
+    def _iter_params(self, cmd):
+        for arg in cmd.args():
+            yield arg
+        for option in cmd.options():
+            if option.name == 'version':
+                continue
+            yield option
 
-        if command.doc:
-            obj['doc'] = unicode(command.doc)
+    def _get_obj(self, cmd, **kwargs):
+        obj = super(command, self)._get_obj(cmd, **kwargs)
 
-        if command.topic:
+        if cmd.doc:
+            obj['doc'] = unicode(cmd.doc)
+
+        if cmd.topic:
             try:
-                topic = self.api.Object.topic.retrieve(unicode(command.topic))
+                topic = self.api.Object.topic.retrieve(unicode(cmd.topic))
             except errors.NotFound:
                 pass
             else:
                 obj['topic_topic'] = topic['name']
 
-        if command.NO_CLI:
+        if cmd.NO_CLI:
             obj['no_cli'] = True
 
-        if len(command.args):
-            obj['args_param'] = tuple(unicode(n) for n in command.args)
+        if len(cmd.args):
+            obj['args_param'] = tuple(unicode(n) for n in cmd.args)
 
-        if len(command.options):
+        if len(cmd.options):
             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(
-                unicode(n) for n in command.output_params)
+                unicode(n) for n in cmd.output_params)
 
         return obj
 
     def _retrieve(self, name, **kwargs):
         try:
-            command = self.api.Command[name]
-            if not isinstance(command, Local):
-                return command
+            cmd = self.api.Command[name]
+            if not isinstance(cmd, Local):
+                return cmd
         except KeyError:
             pass
 
@@ -200,18 +241,18 @@ class command(MetaObject):
         )
 
     def _search(self, **kwargs):
-        for command in self.api.Command():
-            if not isinstance(command, Local):
-                yield command
+        for cmd in self.api.Command():
+            if not isinstance(cmd, Local):
+                yield cmd
 
 
 @register()
-class command_show(MetaRetrieve):
+class command_show(metaobject_show):
     __doc__ = _("Display information about a command.")
 
 
 @register()
-class command_find(MetaSearch):
+class command_find(metaobject_find):
     __doc__ = _("Search for commands.")
 
 
@@ -236,6 +277,52 @@ class command_defaults(PKQuery):
 
 
 @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()
 class topic_(MetaObject):
     name = 'topic'
 
@@ -328,13 +415,17 @@ class BaseParam(BaseMetaObject):
         ),
     )
 
-    def _split_search_args(self, commandname, criteria=None):
-        return [commandname], criteria
+    @property
+    def parent(self):
+        raise AttributeError('parent')
+
+    def _split_search_args(self, parent_name, criteria=None):
+        return [parent_name], criteria
 
 
 class BaseParamMethod(Method):
     def get_args(self):
-        parent = self.api.Object.command
+        parent = self.obj.parent
         parent_key = parent.primary_key
         yield parent_key.clone_rename(
             parent.name + parent_key.name,
@@ -450,6 +541,11 @@ class param(BaseParam):
             flags={'no_search'},
         ),
         Bool(
+            'no_output?',
+            label=_("No output"),
+            flags={'no_search'},
+        ),
+        Bool(
             'suppress_empty?',
             label=_("Suppress empty"),
             flags={'no_search'},
@@ -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['name'] = unicode(param.name)
 
@@ -480,56 +582,62 @@ class param(BaseParam):
             obj['sensitive'] = True
 
         for key, value in param._Param__clonekw.items():
-            if key in ('alwaysask',
-                       'autofill',
-                       'confirm',
-                       'sortorder'):
-                obj[key] = value
-            elif key in ('cli_metavar',
-                         'cli_name',
-                         'doc',
-                         'hint',
-                         'label',
-                         'option_group'):
+            if key in ('doc',
+                       'label'):
                 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 in ('deprecated_cli_aliases',
-                         'exclude',
+            elif key in ('exclude',
                          'include'):
                 obj[key] = list(unicode(v) for v in value)
-            elif key in ('exponential',
-                         'normalizer',
-                         'only_absolute',
-                         'precision'):
-                obj['no_convert'] = True
+            if isinstance(metaobj, Command):
+                if key in ('alwaysask',
+                           'autofill',
+                           'confirm',
+                           '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 []):
-            if flag in ('dnsrecord_extra',
-                        'dnsrecord_part',
-                        'no_option',
+            if flag in ('no_output',
                         'suppress_empty'):
                 obj[flag] = True
+            if isinstance(metaobj, Command):
+                if flag in ('dnsrecord_extra',
+                            'dnsrecord_part',
+                            'no_option'):
+                    obj[flag] = True
 
         return obj
 
-    def _retrieve(self, commandname, name, **kwargs):
-        command = self.api.Command[commandname]
+    def _retrieve(self, metaobjectname, name, **kwargs):
+        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':
-            try:
-                return command.params[name]
-            except KeyError:
-                try:
-                    return command.output_params[name]
-                except KeyError:
-                    pass
+        for param in plugin._iter_params(metaobj):
+            if param.name == name:
+                return metaobj, param
 
         raise errors.NotFound(
             reason=_("%(pkey)s: %(oname)s not found") % {
@@ -537,15 +645,15 @@ class param(BaseParam):
             }
         )
 
-    def _search(self, commandname, **kwargs):
-        command = self.api.Command[commandname]
-
-        result = itertools.chain(
-            (p for p in command.params() if p.name != 'version'),
-            (p for p in command.output_params()
-             if p.name not in command.params))
+    def _search(self, metaobjectname, **kwargs):
+        try:
+            metaobj = self.api.Command[metaobjectname]
+            plugin = self.api.Object['command']
+        except KeyError:
+            metaobj = self.api.Object[metaobjectname]
+            plugin = self.api.Object['class']
 
-        return result
+        return ((metaobj, param) for param in plugin._iter_params(metaobj))
 
 
 @register()
@@ -568,8 +676,12 @@ class output(BaseParam):
         ),
     )
 
-    def _get_obj(self, command_output, **kwargs):
-        command, output = command_output
+    @property
+    def parent(self):
+        return self.api.Object.command
+
+    def _get_obj(self, cmd_output, **kwargs):
+        cmd, output = cmd_output
         required = True
         multivalue = False
 
@@ -577,8 +689,8 @@ class output(BaseParam):
             type_type = dict
             multivalue = isinstance(output, ListOfEntries)
         elif isinstance(output, (PrimaryKey, ListOfPrimaryKeys)):
-            if getattr(command, 'obj', None) and command.obj.primary_key:
-                type_type = command.obj.primary_key.type
+            if getattr(cmd, 'obj', None) and cmd.obj.primary_key:
+                type_type = cmd.obj.primary_key.type
             else:
                 type_type = type(None)
             multivalue = isinstance(output, ListOfPrimaryKeys)
@@ -618,9 +730,9 @@ class output(BaseParam):
         return obj
 
     def _retrieve(self, commandname, name, **kwargs):
-        command = self.api.Command[commandname]
+        cmd = self.api.Command[commandname]
         try:
-            return (command, command.output[name])
+            return (cmd, cmd.output[name])
         except KeyError:
             raise errors.NotFound(
                 reason=_("%(pkey)s: %(oname)s not found") % {
@@ -629,8 +741,8 @@ class output(BaseParam):
             )
 
     def _search(self, commandname, **kwargs):
-        command = self.api.Command[commandname]
-        return ((command, output) for output in command.output())
+        cmd = self.api.Command[commandname]
+        return ((cmd, output) for output in cmd.output())
 
 
 @register()
@@ -656,11 +768,17 @@ class schema(Command):
             command['output'] = list(
                 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))
 
         schema = dict()
         schema['version'] = API_VERSION
         schema['commands'] = commands
+        schema['classes'] = classes
         schema['topics'] = topics
 
         return dict(result=schema)
-- 
2.7.4

From 8af90ca018f9138de6ec7cbb264d4e3767e80ec9 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 16 Jun 2016 13:21:57 +0200
Subject: [PATCH 11/14] schema: remove output_params

Since output params are copied from object plugins, remove them from
command schema and include object name instead.

One exception to this are the output params used for failed members in
member add/remove commands. Move these to the client side, as they will
be replaced by warnings.

https://fedorahosted.org/freeipa/ticket/4739
---
 VERSION                            |   4 +-
 ipaclient/plugins/automember.py    |  35 +++++++++++++
 ipaclient/remote_plugins/schema.py | 100 +++++++++++++++++++++++++++++++++++--
 ipaserver/plugins/schema.py        |  23 +++++----
 4 files changed, 146 insertions(+), 16 deletions(-)
 create mode 100644 ipaclient/plugins/automember.py

diff --git a/VERSION b/VERSION
index a603cbe..24bad7f 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=189
-# Last change: schema: add object class schema
+IPA_API_VERSION_MINOR=190
+# Last change: schema: remove output_params
diff --git a/ipaclient/plugins/automember.py b/ipaclient/plugins/automember.py
new file mode 100644
index 0000000..98caf93
--- /dev/null
+++ b/ipaclient/plugins/automember.py
@@ -0,0 +1,35 @@
+# Authors:
+#   Jr Aquino <jr.aqu...@citrix.com>
+#
+# Copyright (C) 2011  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, either version 3 of the License, or
+# (at your option) any later version.
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+from ipaclient.frontend import MethodOverride
+from ipalib.frontend import Str
+from ipalib.plugable import Registry
+from ipalib.text import _
+
+register = Registry()
+
+
+@register(override=True)
+class automember_add_condition(MethodOverride):
+    has_output_params = (
+        Str('failed',
+        label=_('Failed to add'),
+        flags=['suppress_empty'],
+        ),
+    )
diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py
index d026d44..0ad2d25 100644
--- a/ipaclient/remote_plugins/schema.py
+++ b/ipaclient/remote_plugins/schema.py
@@ -11,10 +11,10 @@ import six
 
 from ipaclient.plugins.rpcclient import rpcclient
 from ipalib import parameters, plugable
-from ipalib.frontend import Command, Object
+from ipalib.frontend import Command, Method, Object
 from ipalib.output import Output
 from ipalib.parameters import Bool, DefaultFrom, Flag, Password, Str
-from ipalib.text import ConcatenatedLazyText
+from ipalib.text import ConcatenatedLazyText, _
 from ipapython.dn import DN
 from ipapython.dnsutil import DNSName
 
@@ -97,6 +97,91 @@ class _SchemaCommand(Command):
             yield option
 
 
+class _SchemaMethod(Method, _SchemaCommand):
+    _failed_member_output_params = (
+        # baseldap
+        Str(
+            'member',
+            label=_("Failed members"),
+        ),
+        Str(
+            'sourcehost',
+            label=_("Failed source hosts/hostgroups"),
+        ),
+        Str(
+            'memberhost',
+            label=_("Failed hosts/hostgroups"),
+        ),
+        Str(
+            'memberuser',
+            label=_("Failed users/groups"),
+        ),
+        Str(
+            'memberservice',
+            label=_("Failed service/service groups"),
+        ),
+        Str(
+            'failed',
+            label=_("Failed to remove"),
+            flags=['suppress_empty'],
+        ),
+        Str(
+            'ipasudorunas',
+            label=_("Failed RunAs"),
+        ),
+        Str(
+            'ipasudorunasgroup',
+            label=_("Failed RunAsGroup"),
+        ),
+        # caacl
+        Str(
+            'ipamembercertprofile',
+            label=_("Failed profiles"),
+        ),
+        Str(
+            'ipamemberca',
+            label=_("Failed CAs"),
+        ),
+        # host
+        Str(
+            'managedby',
+            label=_("Failed managedby"),
+        ),
+        # service
+        Str(
+            'ipaallowedtoperform_read_keys',
+            label=_("Failed allowed to retrieve keytab"),
+        ),
+        Str(
+            'ipaallowedtoperform_write_keys',
+            label=_("Failed allowed to create keytab"),
+        ),
+        # servicedelegation
+        Str(
+            'failed_memberprincipal',
+            label=_("Failed members"),
+        ),
+        Str(
+            'ipaallowedtarget',
+            label=_("Failed targets"),
+        ),
+        # vault
+        Str(
+            'owner?',
+            label=_("Failed owners"),
+        ),
+    )
+
+    def get_output_params(self):
+        seen = set()
+        for output_param in super(_SchemaMethod, self).get_output_params():
+            seen.add(output_param.name)
+            yield output_param
+        for output_param in self._failed_member_output_params:
+            if output_param.name not in seen:
+                yield output_param
+
+
 def _nope():
     pass
 
@@ -212,14 +297,16 @@ def _create_command(schema):
         command['topic'] = str(schema['topic_topic'])
     else:
         command['topic'] = None
+    if 'obj_class' in schema:
+        command['obj_name'] = str(schema['obj_class'])
+    if 'attr_name' in schema:
+        command['attr_name'] = str(schema['attr_name'])
     if 'no_cli' in schema:
         command['NO_CLI'] = schema['no_cli']
     command['takes_args'] = tuple(
         params[n] for n in schema.get('args_param', []))
     command['takes_options'] = tuple(
         params[n] for n in schema.get('options_param', []))
-    command['has_output_params'] = tuple(
-        params[n] for n in schema.get('output_params_param', []))
     command['has_output'] = tuple(
         _create_output(m) for m in schema['output'])
 
@@ -254,7 +341,10 @@ class _LazySchemaPlugin(object):
     @property
     def bases(self):
         if self.__base is Command:
-            return (_SchemaCommand,)
+            if 'obj_class' in self.__schema:
+                return (_SchemaMethod,)
+            else:
+                return (_SchemaCommand,)
         else:
             return (self.__base,)
 
diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index ca662a6..32803a0 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -167,6 +167,16 @@ class metaobject_find(MetaSearch):
 class command(metaobject):
     takes_params = metaobject.takes_params + (
         Str(
+            'obj_class?',
+            label=_("Method of"),
+            flags={'no_search'},
+        ),
+        Str(
+            'attr_name?',
+            label=_("Method name"),
+            flags={'no_search'},
+        ),
+        Str(
             'args_param*',
             label=_("Arguments"),
             flags={'no_search'},
@@ -176,11 +186,6 @@ class command(metaobject):
             label=_("Options"),
             flags={'no_search'},
         ),
-        Str(
-            'output_params_param*',
-            label=_("Output parameters"),
-            flags={'no_search'},
-        ),
         Bool(
             'no_cli?',
             label=_("Exclude from CLI"),
@@ -210,6 +215,10 @@ class command(metaobject):
             else:
                 obj['topic_topic'] = topic['name']
 
+        if isinstance(cmd, Method):
+            obj['obj_class'] = unicode(cmd.obj_name)
+            obj['attr_name'] = unicode(cmd.attr_name)
+
         if cmd.NO_CLI:
             obj['no_cli'] = True
 
@@ -220,10 +229,6 @@ class command(metaobject):
             obj['options_param'] = tuple(
                 unicode(n) for n in cmd.options if n != 'version')
 
-        if len(cmd.output_params):
-            obj['output_params_param'] = tuple(
-                unicode(n) for n in cmd.output_params)
-
         return obj
 
     def _retrieve(self, name, **kwargs):
-- 
2.7.4

From 10cb85bd9d54bc1e928d624aace97864cf9130a7 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Thu, 16 Jun 2016 10:19:25 +0200
Subject: [PATCH 12/14] schema: merge command args and options

Rather than having args and options separately in command schema, merge
them together and use new `positional` param flag to differentiate between
them.

https://fedorahosted.org/freeipa/ticket/4739
---
 VERSION                            |  4 ++--
 ipaclient/remote_plugins/schema.py | 11 +++++------
 ipaserver/plugins/schema.py        | 27 ++++++++++-----------------
 3 files changed, 17 insertions(+), 25 deletions(-)

diff --git a/VERSION b/VERSION
index 24bad7f..3478231 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=190
-# Last change: schema: remove output_params
+IPA_API_VERSION_MINOR=191
+# Last change: schema: merge command args and options
diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py
index 0ad2d25..25a2a7e 100644
--- a/ipaclient/remote_plugins/schema.py
+++ b/ipaclient/remote_plugins/schema.py
@@ -286,11 +286,8 @@ def _create_output(schema):
 
 
 def _create_command(schema):
-    name = str(schema['name'])
-    params = {m['name']: _create_param(m) for m in schema['params']}
-
     command = {}
-    command['name'] = name
+    command['name'] = str(schema['name'])
     if 'doc' in schema:
         command['doc'] = ConcatenatedLazyText(schema['doc'])
     if 'topic_topic' in schema:
@@ -304,9 +301,11 @@ def _create_command(schema):
     if 'no_cli' in schema:
         command['NO_CLI'] = schema['no_cli']
     command['takes_args'] = tuple(
-        params[n] for n in schema.get('args_param', []))
+        _create_param(s) for s in schema['params']
+        if s.get('positional', s.get('required', True)))
     command['takes_options'] = tuple(
-        params[n] for n in schema.get('options_param', []))
+        _create_param(s) for s in schema['params']
+        if not s.get('positional', s.get('required', True)))
     command['has_output'] = tuple(
         _create_output(m) for m in schema['output'])
 
diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index 32803a0..42806e7 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -176,16 +176,6 @@ class command(metaobject):
             label=_("Method name"),
             flags={'no_search'},
         ),
-        Str(
-            'args_param*',
-            label=_("Arguments"),
-            flags={'no_search'},
-        ),
-        Str(
-            'options_param*',
-            label=_("Options"),
-            flags={'no_search'},
-        ),
         Bool(
             'no_cli?',
             label=_("Exclude from CLI"),
@@ -222,13 +212,6 @@ class command(metaobject):
         if cmd.NO_CLI:
             obj['no_cli'] = True
 
-        if len(cmd.args):
-            obj['args_param'] = tuple(unicode(n) for n in cmd.args)
-
-        if len(cmd.options):
-            obj['options_param'] = tuple(
-                unicode(n) for n in cmd.options if n != 'version')
-
         return obj
 
     def _retrieve(self, name, **kwargs):
@@ -560,6 +543,11 @@ class param(BaseParam):
             label=_("Sensitive"),
             flags={'no_search'},
         ),
+        Bool(
+            'positional?',
+            label=_("Positional argument"),
+            flags={'no_search'},
+        ),
     )
 
     @property
@@ -585,6 +573,11 @@ class param(BaseParam):
             obj['multivalue'] = True
         if param.password:
             obj['sensitive'] = True
+        if isinstance(metaobj, Command):
+            if param.required and param.name not in metaobj.args:
+                obj['positional'] = False
+            elif not param.required and param.name in metaobj.args:
+                obj['positional'] = True
 
         for key, value in param._Param__clonekw.items():
             if key in ('doc',
-- 
2.7.4

From 4bcc2ca228f827da0c5434177e9b721928453eb1 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Wed, 8 Jun 2016 16:24:45 +0200
Subject: [PATCH 13/14] schema: remove redundant information

Remove the `autofill` kwarg from param schema. On the server, include
default value only if autofill is set. On the client, set autofill if param
has a default value.

Remove the `deprecated_cli_aliases`, `hint` and `sortorder` kwargs, and the
`dnsrecord_extra`, `dnsrecord_part` and `suppress_empty` flags from param
schema, as they are now handled exclusively on the client.

Replace the `no_option` and `no_output` flags in param schema with
exclusion of the param in 'cli' and 'webui' contexts.

Remove the `no_display` flag from output schema, as it is now handled
exclusively on the client.

https://fedorahosted.org/freeipa/ticket/4739
---
 VERSION                            |  4 +-
 ipaclient/remote_plugins/schema.py | 13 ++----
 ipaserver/plugins/schema.py        | 96 +++++++-------------------------------
 3 files changed, 23 insertions(+), 90 deletions(-)

diff --git a/VERSION b/VERSION
index 3478231..fed0c74 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=191
-# Last change: schema: merge command args and options
+IPA_API_VERSION_MINOR=192
+# Last change: schema: remove redundant information
diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py
index 25a2a7e..1a9dcd1 100644
--- a/ipaclient/remote_plugins/schema.py
+++ b/ipaclient/remote_plugins/schema.py
@@ -223,25 +223,20 @@ def _create_param(meta):
                    'sortorder'):
             kwargs[key] = value
         elif key in ('cli_metavar',
-                     'cli_name',
-                     'hint'):
+                     'cli_name'):
             kwargs[key] = str(value)
         elif key == 'confirm' and issubclass(cls, parameters.Password):
             kwargs[key] = value
         elif key == 'default':
             default = value
+            kwargs['autofill'] = True
         elif key == 'default_from_param':
             kwargs['default_from'] = DefaultFrom(_nope,
                                                  *(str(k) for k in value))
-        elif key in ('deprecated_cli_aliases',
-                     'exclude',
+            kwargs['autofill'] = True
+        elif key in ('exclude',
                      'include'):
             kwargs[key] = tuple(str(v) for v in value)
-        elif key in ('dnsrecord_extra',
-                     'dnsrecord_part',
-                     'no_option',
-                     'suppress_empty') and value:
-            kwargs.setdefault('flags', set()).add(key)
 
     if default is not None:
         tmp = cls(str(meta['name']), **dict(kwargs, no_convert=False))
diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index 42806e7..0216463 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -13,7 +13,7 @@ from ipalib import errors
 from ipalib.crud import PKQuery, Retrieve, Search
 from ipalib.frontend import Command, Local, Method, Object
 from ipalib.output import Entry, ListOfEntries, ListOfPrimaryKeys, PrimaryKey
-from ipalib.parameters import Bool, Dict, Flag, Int, Str
+from ipalib.parameters import Bool, Dict, Flag, Str
 from ipalib.plugable import Registry
 from ipalib.text import _
 from ipapython.version import API_VERSION
@@ -443,11 +443,6 @@ class param(BaseParam):
             label=_("Always ask"),
             flags={'no_search'},
         ),
-        Bool(
-            'autofill?',
-            label=_("Autofill"),
-            flags={'no_search'},
-        ),
         Str(
             'cli_metavar?',
             label=_("CLI metavar"),
@@ -474,21 +469,11 @@ class param(BaseParam):
             flags={'no_search'},
         ),
         Str(
-            'deprecated_cli_aliases*',
-            label=_("Deprecated CLI aliases"),
-            flags={'no_search'},
-        ),
-        Str(
             'exclude*',
             label=_("Exclude from"),
             flags={'no_search'},
         ),
         Str(
-            'hint?',
-            label=_("Hint"),
-            flags={'no_search'},
-        ),
-        Str(
             'include*',
             label=_("Include in"),
             flags={'no_search'},
@@ -508,36 +493,6 @@ class param(BaseParam):
             label=_("Option group"),
             flags={'no_search'},
         ),
-        Int(
-            'sortorder?',
-            label=_("Sort order"),
-            flags={'no_search'},
-        ),
-        Bool(
-            'dnsrecord_extra?',
-            label=_("Extra field (DNS record)"),
-            flags={'no_search'},
-        ),
-        Bool(
-            'dnsrecord_part?',
-            label=_("Part (DNS record)"),
-            flags={'no_search'},
-        ),
-        Bool(
-            'no_option?',
-            label=_("No option"),
-            flags={'no_search'},
-        ),
-        Bool(
-            'no_output?',
-            label=_("No output"),
-            flags={'no_search'},
-        ),
-        Bool(
-            'suppress_empty?',
-            label=_("Suppress empty"),
-            flags={'no_search'},
-        ),
         Bool(
             'sensitive?',
             label=_("Sensitive"),
@@ -588,40 +543,35 @@ class param(BaseParam):
                 obj[key] = list(unicode(v) for v in value)
             if isinstance(metaobj, Command):
                 if key in ('alwaysask',
-                           'autofill',
-                           'confirm',
-                           'sortorder'):
+                           'confirm'):
                     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)]
+                    if param.autofill:
+                        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)
+                    if param.autofill:
+                        obj['default_from_param'] = list(unicode(k)
+                                                         for k in value.keys)
                 elif key in ('exponential',
                              'normalizer',
                              'only_absolute',
                              'precision'):
                     obj['no_convert'] = True
 
-        for flag in (param.flags or []):
-            if flag in ('no_output',
-                        'suppress_empty'):
-                obj[flag] = True
-            if isinstance(metaobj, Command):
-                if flag in ('dnsrecord_extra',
-                            'dnsrecord_part',
-                            'no_option'):
-                    obj[flag] = True
+        if ((isinstance(metaobj, Command) and 'no_option' in param.flags) or
+                (isinstance(metaobj, Object) and 'no_output' in param.flags)):
+            value = obj.setdefault('exclude', [])
+            if u'cli' not in value:
+                value.append(u'cli')
+            if u'webui' not in value:
+                value.append(u'webui')
 
         return obj
 
@@ -666,14 +616,6 @@ class param_find(BaseParamSearch):
 
 @register()
 class output(BaseParam):
-    takes_params = BaseParam.takes_params + (
-        Bool(
-            'no_display?',
-            label=_("Do not display"),
-            flags={'no_search'},
-        ),
-    )
-
     @property
     def parent(self):
         return self.api.Object.command
@@ -721,10 +663,6 @@ class output(BaseParam):
         if 'doc' in output.__dict__:
             obj['doc'] = unicode(output.doc)
 
-        if 'flags' in output.__dict__:
-            if 'no_display' in output.flags:
-                obj['no_display'] = True
-
         return obj
 
     def _retrieve(self, commandname, name, **kwargs):
-- 
2.7.4

From dbcf3a894f858ce3d56ce5c1ec7b32993ed15951 Mon Sep 17 00:00:00 2001
From: Jan Cholasta <jchol...@redhat.com>
Date: Mon, 20 Jun 2016 12:56:28 +0200
Subject: [PATCH 14/14] schema: remove `no_cli` from command schema

Instead, support excluding commands from specified contexts and exclude
commands with NO_CLI set from the 'cli' context.

https://fedorahosted.org/freeipa/ticket/4739
---
 VERSION                            |  4 ++--
 ipaclient/remote_plugins/schema.py |  4 ++--
 ipaserver/plugins/schema.py        | 27 +++++++++++----------------
 3 files changed, 15 insertions(+), 20 deletions(-)

diff --git a/VERSION b/VERSION
index fed0c74..faf10e3 100644
--- a/VERSION
+++ b/VERSION
@@ -90,5 +90,5 @@ IPA_DATA_VERSION=20100614120000
 #                                                      #
 ########################################################
 IPA_API_VERSION_MAJOR=2
-IPA_API_VERSION_MINOR=192
-# Last change: schema: remove redundant information
+IPA_API_VERSION_MINOR=193
+# Last change: schema: remove `no_cli` from command schema
diff --git a/ipaclient/remote_plugins/schema.py b/ipaclient/remote_plugins/schema.py
index 1a9dcd1..c21da5f 100644
--- a/ipaclient/remote_plugins/schema.py
+++ b/ipaclient/remote_plugins/schema.py
@@ -293,8 +293,8 @@ def _create_command(schema):
         command['obj_name'] = str(schema['obj_class'])
     if 'attr_name' in schema:
         command['attr_name'] = str(schema['attr_name'])
-    if 'no_cli' in schema:
-        command['NO_CLI'] = schema['no_cli']
+    if 'exclude' in schema and u'cli' in schema['exclude']:
+        command['NO_CLI'] = True
     command['takes_args'] = tuple(
         _create_param(s) for s in schema['params']
         if s.get('positional', s.get('required', True)))
diff --git a/ipaserver/plugins/schema.py b/ipaserver/plugins/schema.py
index 0216463..98b47cb 100644
--- a/ipaserver/plugins/schema.py
+++ b/ipaserver/plugins/schema.py
@@ -52,6 +52,16 @@ class BaseMetaObject(Object):
             label=_("Documentation"),
             flags={'no_search'},
         ),
+        Str(
+            'exclude*',
+            label=_("Exclude from"),
+            flags={'no_search'},
+        ),
+        Str(
+            'include*',
+            label=_("Include in"),
+            flags={'no_search'},
+        ),
     )
 
     def _get_obj(self, obj, **kwargs):
@@ -176,11 +186,6 @@ class command(metaobject):
             label=_("Method name"),
             flags={'no_search'},
         ),
-        Bool(
-            'no_cli?',
-            label=_("Exclude from CLI"),
-            flags={'no_search'},
-        ),
     )
 
     def _iter_params(self, cmd):
@@ -210,7 +215,7 @@ class command(metaobject):
             obj['attr_name'] = unicode(cmd.attr_name)
 
         if cmd.NO_CLI:
-            obj['no_cli'] = True
+            obj['exclude'] = [u'cli']
 
         return obj
 
@@ -469,16 +474,6 @@ class param(BaseParam):
             flags={'no_search'},
         ),
         Str(
-            'exclude*',
-            label=_("Exclude from"),
-            flags={'no_search'},
-        ),
-        Str(
-            'include*',
-            label=_("Include in"),
-            flags={'no_search'},
-        ),
-        Str(
             'label?',
             label=_("Label"),
             flags={'no_search'},
-- 
2.7.4

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to