From d97386de5b68c90c53362dda54b126fdc97e00b6 Mon Sep 17 00:00:00 2001 From: Ana Krivokapic Date: Thu, 19 Sep 2013 14:01:58 +0200 Subject: [PATCH] Add automember rebuild command Add a new command to IPA CLI: ipa automember-rebuild The command integrates the automember rebuild membership task functionality into IPA CLI. It makes it possible to rebuild automember membership for groups/hostgroups. Design: http://www.freeipa.org/page/V3/Automember_rebuild_membership https://fedorahosted.org/freeipa/ticket/3752 --- API.txt | 9 +++ VERSION | 2 +- ipalib/plugins/automember.py | 143 ++++++++++++++++++++++++++++++++--- 3 files changed, 143 insertions(+), 11 deletions(-) diff --git a/API.txt b/API.txt index cddb9d719f3..b3b3a7e381b 100644 --- a/API.txt +++ b/API.txt @@ -199,6 +199,15 @@ option: Str('version?', exclude='webui') output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('summary', (, ), None) output: Output('value', , None) +command: automember_rebuild +args: 0,4,3 +option: Str('hosts*') +option: StrEnum('type', cli_name='type', multivalue=False, required=False, values=(u'group', u'hostgroup')) +option: Str('users*') +option: Str('version?', exclude='webui') +output: Output('result', , None) +output: Output('summary', (, ), None) +output: Output('value', , None) command: automember_remove_condition args: 1,8,5 arg: Str('cn', cli_name='automember_rule') diff --git a/VERSION b/VERSION index 32f6efbc4d4..0dacb97041d 100644 --- a/VERSION +++ b/VERSION @@ -89,4 +89,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=66 +IPA_API_VERSION_MINOR=67 diff --git a/ipalib/plugins/automember.py b/ipalib/plugins/automember.py index 4f563f11953..fc696cc62ba 100644 --- a/ipalib/plugins/automember.py +++ b/ipalib/plugins/automember.py @@ -16,13 +16,11 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . - -from ipalib import api, errors -from ipalib import Str, StrEnum +import uuid +import ldap as _ldap +from ipalib import api, errors, Str, StrEnum, _, ngettext from ipalib.plugins.baseldap import * -from ipalib import _, ngettext from ipalib.request import context -import ldap as _ldap from ipapython.dn import DN __doc__ = _(""" @@ -43,6 +41,8 @@ match any rule. In case of user entries this group will be a fallback group because all users are by default members of group specified in IPA config. +The automember-rebuild command can be used to retroactively run automember rules +against existing entries, thus rebuilding their membership. EXAMPLES: @@ -107,6 +107,18 @@ Delete an automember rule: ipa automember-del --type=hostgroup webservers ipa automember-del --type=group devel + + Rebuild membership for all users: + ipa automember-rebuild --type=group + + Rebuild membership for all hosts: + ipa automember-rebuild --type=hostgroup + + Rebuild membership for specified users: + ipa automember-rebuild --users=tuser1 --users=tuser2 + + Rebuild membership for specified hosts: + ipa automember-rebuild --hosts=web1.example.com --hosts=web2.example.com """) # Options used by Condition Add and Remove. @@ -184,14 +196,17 @@ class automember(LDAPObject): ), ) - def dn_exists(self, grouptype, groupname, *keys): + def dn_exists(self, otype, oname): ldap = self.api.Backend.ldap2 - dn = self.api.Object[grouptype].get_dn(groupname) + dn = self.api.Object[otype].get_dn(oname) try: - (gdn, entry_attrs) = ldap.get_entry(dn, []) + entry = ldap.get_entry(dn, []) except errors.NotFound: - raise errors.NotFound(reason=_(u'Group: %s not found!') % groupname) - return gdn + raise errors.NotFound( + reason=_(u'%(otype)s "%(oname)s" not found') % + dict(otype=otype, oname=oname) + ) + return entry.dn def get_dn(self, *keys, **options): if self.parent_object: @@ -587,3 +602,111 @@ def execute(self, *keys, **options): return result api.register(automember_default_group_show) + + +class automember_rebuild(Command): + __doc__ = _('Rebuild auto membership.') + # TODO: Add a --dry-run option: + # https://fedorahosted.org/freeipa/ticket/3936 + takes_options = ( + group_type[0].clone( + required=False, + label=_('Rebuild membership for all members of a grouping') + ), + Str( + 'users*', + label=_('Users'), + doc=_('Rebuild membership for specified users'), + ), + Str( + 'hosts*', + label=_('Hosts'), + doc=_('Rebuild membership for specified hosts'), + ), + ) + has_output = output.standard_value + msg_summary = _('Automember rebuild membership task completed') + + def validate(self, **kw): + """ + Validation rules: + - at least one of 'type', 'users', 'hosts' is required + - 'users' and 'hosts' cannot be combined together + - if 'users' and 'type' are specified, 'type' must be 'group' + - if 'hosts' and 'type' are specified, 'type' must be 'hostgroup' + """ + super(automember_rebuild, self).validate(**kw) + users, hosts, gtype = kw.get('users'), kw.get('hosts'), kw.get('type') + + if not (gtype or users or hosts): + raise errors.MutuallyExclusiveError( + reason=_('at least one of options: type, users, hosts must be ' + 'specified') + ) + + if users and hosts: + raise errors.MutuallyExclusiveError( + reason=_("users and hosts cannot both be set") + ) + if gtype == 'group' and hosts: + raise errors.MutuallyExclusiveError( + reason=_("hosts cannot be set when type is 'group'") + ) + if gtype == 'hostgroup' and users: + raise errors.MutuallyExclusiveError( + reason=_("users cannot be set when type is 'hostgroup'") + ) + + def execute(self, *keys, **options): + ldap = self.api.Backend.ldap2 + cn = str(uuid.uuid4()) + + gtype = options.get('type') + if not gtype: + gtype = 'group' if options.get('users') else 'hostgroup' + + types = { + 'group': ( + 'user', + 'users', + DN(api.env.container_user, api.env.basedn) + ), + 'hostgroup': ( + 'host', + 'hosts', + DN(api.env.container_host, api.env.basedn) + ), + } + + obj_name, opt_name, basedn = types[gtype] + obj = self.api.Object[obj_name] + + names = options.get(opt_name) + if names: + for name in names: + obj.get_dn_if_exists(name) + search_filter = ldap.make_filter_from_attr( + obj.primary_key.name, + names, + rules=ldap.MATCH_ANY + ) + else: + search_filter = '(%s=*)' % obj.primary_key.name + + entry = ldap.make_entry( + DN( + ('cn', cn), + ('cn', 'automember rebuild membership'), + ('cn', 'tasks'), + ('cn', 'config'), + ), + objectclass=['top', 'extensibleObject'], + cn=[cn], + basedn=[basedn], + filter=[search_filter], + scope=['sub'] + ) + ldap.add_entry(entry) + return dict(result=True, value=u'') + +api.register(automember_rebuild)