summaryrefslogtreecommitdiffstats
path: root/ipaserver/plugins/automember.py
diff options
context:
space:
mode:
authorJan Cholasta <jcholast@redhat.com>2016-04-28 10:30:05 +0200
committerJan Cholasta <jcholast@redhat.com>2016-06-03 09:00:34 +0200
commit6e44557b601f769d23ee74555a72e8b5cc62c0c9 (patch)
treeeedd3e054b0709341b9f58c190ea54f999f7d13a /ipaserver/plugins/automember.py
parentec841e5d7ab29d08de294b3fa863a631cd50e30a (diff)
downloadfreeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.gz
freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.tar.xz
freeipa-6e44557b601f769d23ee74555a72e8b5cc62c0c9.zip
ipalib: move server-side plugins to ipaserver
Move the remaining plugin code from ipalib.plugins to ipaserver.plugins. Remove the now unused ipalib.plugins package. https://fedorahosted.org/freeipa/ticket/4739 Reviewed-By: David Kupka <dkupka@redhat.com>
Diffstat (limited to 'ipaserver/plugins/automember.py')
-rw-r--r--ipaserver/plugins/automember.py802
1 files changed, 802 insertions, 0 deletions
diff --git a/ipaserver/plugins/automember.py b/ipaserver/plugins/automember.py
new file mode 100644
index 000000000..89b9dfadc
--- /dev/null
+++ b/ipaserver/plugins/automember.py
@@ -0,0 +1,802 @@
+# Authors:
+# Jr Aquino <jr.aquino@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/>.
+import uuid
+import time
+
+import ldap as _ldap
+import six
+
+from ipalib import api, errors, Str, StrEnum, DNParam, Flag, _, ngettext
+from ipalib import output, Command
+from ipalib.plugable import Registry
+from .baseldap import (
+ pkey_to_value,
+ entry_to_dict,
+ LDAPObject,
+ LDAPCreate,
+ LDAPUpdate,
+ LDAPDelete,
+ LDAPSearch,
+ LDAPRetrieve)
+from ipalib.request import context
+from ipapython.dn import DN
+
+if six.PY3:
+ unicode = str
+
+__doc__ = _("""
+Auto Membership Rule.
+""") + _("""
+Bring clarity to the membership of hosts and users by configuring inclusive
+or exclusive regex patterns, you can automatically assign a new entries into
+a group or hostgroup based upon attribute information.
+""") + _("""
+A rule is directly associated with a group by name, so you cannot create
+a rule without an accompanying group or hostgroup.
+""") + _("""
+A condition is a regular expression used by 389-ds to match a new incoming
+entry with an automember rule. If it matches an inclusive rule then the
+entry is added to the appropriate group or hostgroup.
+""") + _("""
+A default group or hostgroup could be specified for entries that do not
+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:
+""") + _("""
+ Add the initial group or hostgroup:
+ ipa hostgroup-add --desc="Web Servers" webservers
+ ipa group-add --desc="Developers" devel
+""") + _("""
+ Add the initial rule:
+ ipa automember-add --type=hostgroup webservers
+ ipa automember-add --type=group devel
+""") + _("""
+ Add a condition to the rule:
+ ipa automember-add-condition --key=fqdn --type=hostgroup --inclusive-regex=^web[1-9]+\.example\.com webservers
+ ipa automember-add-condition --key=manager --type=group --inclusive-regex=^uid=mscott devel
+""") + _("""
+ Add an exclusive condition to the rule to prevent auto assignment:
+ ipa automember-add-condition --key=fqdn --type=hostgroup --exclusive-regex=^web5\.example\.com webservers
+""") + _("""
+ Add a host:
+ ipa host-add web1.example.com
+""") + _("""
+ Add a user:
+ ipa user-add --first=Tim --last=User --password tuser1 --manager=mscott
+""") + _("""
+ Verify automembership:
+ ipa hostgroup-show webservers
+ Host-group: webservers
+ Description: Web Servers
+ Member hosts: web1.example.com
+
+ ipa group-show devel
+ Group name: devel
+ Description: Developers
+ GID: 1004200000
+ Member users: tuser
+""") + _("""
+ Remove a condition from the rule:
+ ipa automember-remove-condition --key=fqdn --type=hostgroup --inclusive-regex=^web[1-9]+\.example\.com webservers
+""") + _("""
+ Modify the automember rule:
+ ipa automember-mod
+""") + _("""
+ Set the default (fallback) target group:
+ ipa automember-default-group-set --default-group=webservers --type=hostgroup
+ ipa automember-default-group-set --default-group=ipausers --type=group
+""") + _("""
+ Remove the default (fallback) target group:
+ ipa automember-default-group-remove --type=hostgroup
+ ipa automember-default-group-remove --type=group
+""") + _("""
+ Show the default (fallback) target group:
+ ipa automember-default-group-show --type=hostgroup
+ ipa automember-default-group-show --type=group
+""") + _("""
+ Find all of the automember rules:
+ ipa automember-find
+""") + _("""
+ Display a automember rule:
+ ipa automember-show --type=hostgroup webservers
+ ipa automember-show --type=group devel
+""") + _("""
+ 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
+""")
+
+register = Registry()
+
+# Options used by Condition Add and Remove.
+INCLUDE_RE = 'automemberinclusiveregex'
+EXCLUDE_RE = 'automemberexclusiveregex'
+
+REBUILD_TASK_CONTAINER = DN(('cn', 'automember rebuild membership'),
+ ('cn', 'tasks'),
+ ('cn', 'config'))
+
+
+regex_attrs = (
+ Str('automemberinclusiveregex*',
+ cli_name='inclusive_regex',
+ label=_('Inclusive Regex'),
+ doc=_('Inclusive Regex'),
+ alwaysask=True,
+ ),
+ Str('automemberexclusiveregex*',
+ cli_name='exclusive_regex',
+ label=_('Exclusive Regex'),
+ doc=_('Exclusive Regex'),
+ alwaysask=True,
+ ),
+ Str('key',
+ label=_('Attribute Key'),
+ doc=_('Attribute to filter via regex. For example fqdn for a host, or manager for a user'),
+ flags=['no_create', 'no_update', 'no_search']
+ ),
+)
+
+group_type = (
+ StrEnum('type',
+ label=_('Grouping Type'),
+ doc=_('Grouping to which the rule applies'),
+ values=(u'group', u'hostgroup', ),
+ ),
+)
+
+automember_rule = (
+ Str('cn',
+ cli_name='automember_rule',
+ label=_('Automember Rule'),
+ doc=_('Automember Rule'),
+ normalizer=lambda value: value.lower(),
+ ),
+)
+
+
+@register()
+class automember(LDAPObject):
+
+ """
+ Bring automember to a hostgroup with an Auto Membership Rule.
+ """
+
+ container_dn = api.env.container_automember
+
+ object_name = 'Automember rule'
+ object_name_plural = 'Automember rules'
+ object_class = ['top', 'automemberregexrule']
+ permission_filter_objectclasses = ['automemberregexrule']
+ default_attributes = [
+ 'automemberinclusiveregex', 'automemberexclusiveregex',
+ 'cn', 'automembertargetgroup', 'description', 'automemberdefaultgroup'
+ ]
+ managed_permissions = {
+ 'System: Read Automember Definitions': {
+ 'non_object': True,
+ 'ipapermlocation': DN(container_dn, api.env.basedn),
+ 'ipapermtargetfilter': {'(objectclass=automemberdefinition)'},
+ 'replaces_global_anonymous_aci': True,
+ 'ipapermright': {'read', 'search', 'compare'},
+ 'ipapermdefaultattr': {
+ 'objectclass', 'cn', 'automemberscope', 'automemberfilter',
+ 'automembergroupingattr', 'automemberdefaultgroup',
+ 'automemberdisabled',
+ },
+ 'default_privileges': {'Automember Readers',
+ 'Automember Task Administrator'},
+ },
+ 'System: Read Automember Rules': {
+ 'replaces_global_anonymous_aci': True,
+ 'ipapermright': {'read', 'search', 'compare'},
+ 'ipapermdefaultattr': {
+ 'cn', 'objectclass', 'automembertargetgroup', 'description',
+ 'automemberexclusiveregex', 'automemberinclusiveregex',
+ },
+ 'default_privileges': {'Automember Readers',
+ 'Automember Task Administrator'},
+ },
+ 'System: Read Automember Tasks': {
+ 'non_object': True,
+ 'ipapermlocation': DN('cn=tasks', 'cn=config'),
+ 'ipapermtarget': DN('cn=*', REBUILD_TASK_CONTAINER),
+ 'replaces_global_anonymous_aci': True,
+ 'ipapermright': {'read', 'search', 'compare'},
+ 'ipapermdefaultattr': {'*'},
+ 'default_privileges': {'Automember Task Administrator'},
+ },
+ }
+
+ label = _('Auto Membership Rule')
+
+ takes_params = (
+ Str('description?',
+ cli_name='desc',
+ label=_('Description'),
+ doc=_('A description of this auto member rule'),
+ ),
+ Str('automemberdefaultgroup?',
+ cli_name='default_group',
+ label=_('Default (fallback) Group'),
+ doc=_('Default group for entries to land'),
+ flags=['no_create', 'no_update', 'no_search']
+ ),
+ )
+
+ def dn_exists(self, otype, oname):
+ ldap = self.api.Backend.ldap2
+ dn = self.api.Object[otype].get_dn(oname)
+ try:
+ entry = ldap.get_entry(dn, [])
+ except errors.NotFound:
+ 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:
+ parent_dn = self.api.Object[self.parent_object].get_dn(*keys[:-1])
+ else:
+ parent_dn = DN(self.container_dn, api.env.basedn)
+ grouptype = options['type']
+ try:
+ ndn = DN(('cn', keys[-1]), ('cn', grouptype), parent_dn)
+ except IndexError:
+ ndn = DN(('cn', grouptype), parent_dn)
+ return ndn
+
+ def check_attr(self, attr):
+ """
+ Verify that the user supplied key is a valid attribute in the schema
+ """
+ ldap = self.api.Backend.ldap2
+ obj = ldap.schema.get_obj(_ldap.schema.AttributeType, attr)
+ if obj is not None:
+ return obj
+ else:
+ raise errors.NotFound(reason=_('%s is not a valid attribute.') % attr)
+
+
+def automember_container_exists(ldap):
+ try:
+ ldap.get_entry(DN(api.env.container_automember, api.env.basedn), [])
+ except errors.NotFound:
+ return False
+ return True
+
+
+@register()
+class automember_add(LDAPCreate):
+ __doc__ = _("""
+ Add an automember rule.
+ """)
+ takes_options = LDAPCreate.takes_options + group_type
+ takes_args = automember_rule
+ msg_summary = _('Added automember rule "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ assert isinstance(dn, DN)
+
+ entry_attrs['cn'] = keys[-1]
+ if not automember_container_exists(self.api.Backend.ldap2):
+ raise errors.NotFound(reason=_('Auto Membership is not configured'))
+ entry_attrs['automembertargetgroup'] = self.obj.dn_exists(options['type'], keys[-1])
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_add, self).execute(*keys, **options)
+ result['value'] = pkey_to_value(keys[-1], options)
+ return result
+
+
+@register()
+class automember_add_condition(LDAPUpdate):
+ __doc__ = _("""
+ Add conditions to an automember rule.
+ """)
+ has_output_params = (
+ Str('failed',
+ label=_('Failed to add'),
+ flags=['suppress_empty'],
+ ),
+ )
+
+ takes_options = regex_attrs + group_type
+ takes_args = automember_rule
+ msg_summary = _('Added condition(s) to "%(value)s"')
+
+ # Prepare the output to expect failed results
+ has_output = (
+ output.summary,
+ output.Entry('result'),
+ output.value,
+ output.Output('failed',
+ type=dict,
+ doc=_('Conditions that could not be added'),
+ ),
+ output.Output('completed',
+ type=int,
+ doc=_('Number of conditions added'),
+ ),
+ )
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ assert isinstance(dn, DN)
+ # Check to see if the automember rule exists
+ try:
+ dn = ldap.get_entry(dn, []).dn
+ except errors.NotFound:
+ raise errors.NotFound(reason=_(u'Auto member rule: %s not found!') % keys[0])
+ # Define container key
+ key = options['key']
+ # Check to see if the attribute is valid
+ self.obj.check_attr(key)
+
+ key = '%s=' % key
+ completed = 0
+ failed = {'failed': {}}
+
+ for attr in (INCLUDE_RE, EXCLUDE_RE):
+ failed['failed'][attr] = []
+ if attr in options and options[attr]:
+ entry_attrs[attr] = [key + condition for condition in options[attr]]
+ completed += len(entry_attrs[attr])
+ try:
+ old_entry = ldap.get_entry(dn, [attr])
+ for regex in old_entry.keys():
+ if not isinstance(entry_attrs[regex], (list, tuple)):
+ entry_attrs[regex] = [entry_attrs[regex]]
+ duplicate = set(old_entry[regex]) & set(entry_attrs[regex])
+ if len(duplicate) > 0:
+ completed -= 1
+ else:
+ entry_attrs[regex] = list(entry_attrs[regex]) + old_entry[regex]
+ except errors.NotFound:
+ failed['failed'][attr].append(regex)
+
+ entry_attrs = entry_to_dict(entry_attrs, **options)
+
+ # Set failed and completed to they can be harvested in the execute super
+ setattr(context, 'failed', failed)
+ setattr(context, 'completed', completed)
+ setattr(context, 'entry_attrs', entry_attrs)
+
+ # Make sure to returned the failed results if there is nothing to remove
+ if completed == 0:
+ ldap.get_entry(dn, attrs_list)
+ raise errors.EmptyModlist
+ return dn
+
+ def execute(self, *keys, **options):
+ __doc__ = _("""
+ Override this so we can add completed and failed to the return result.
+ """)
+ try:
+ result = super(automember_add_condition, self).execute(*keys, **options)
+ except errors.EmptyModlist:
+ result = {'result': getattr(context, 'entry_attrs'), 'value': keys[-1]}
+ result['failed'] = getattr(context, 'failed')
+ result['completed'] = getattr(context, 'completed')
+ result['value'] = pkey_to_value(keys[-1], options)
+ return result
+
+
+@register()
+class automember_remove_condition(LDAPUpdate):
+ __doc__ = _("""
+ Remove conditions from an automember rule.
+ """)
+ takes_options = regex_attrs + group_type
+ takes_args = automember_rule
+ msg_summary = _('Removed condition(s) from "%(value)s"')
+
+ # Prepare the output to expect failed results
+ has_output = (
+ output.summary,
+ output.Entry('result'),
+ output.value,
+ output.Output('failed',
+ type=dict,
+ doc=_('Conditions that could not be removed'),
+ ),
+ output.Output('completed',
+ type=int,
+ doc=_('Number of conditions removed'),
+ ),
+ )
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ assert isinstance(dn, DN)
+ # Check to see if the automember rule exists
+ try:
+ ldap.get_entry(dn, [])
+ except errors.NotFound:
+ raise errors.NotFound(reason=_(u'Auto member rule: %s not found!') % keys[0])
+
+ # Define container key
+ type_attr_default = {'group': 'manager', 'hostgroup': 'fqdn'}
+ if 'key' in options:
+ key = options['key']
+ else:
+ key = type_attr_default[options['type']]
+
+ key = '%s=' % key
+ completed = 0
+ failed = {'failed': {}}
+
+ # Check to see if there are existing exclusive conditions present.
+ dn = ldap.get_entry(dn, [EXCLUDE_RE]).dn
+
+ for attr in (INCLUDE_RE, EXCLUDE_RE):
+ failed['failed'][attr] = []
+ if attr in options and options[attr]:
+ entry_attrs[attr] = [key + condition for condition in options[attr]]
+ entry_attrs_ = ldap.get_entry(dn, [attr])
+ old_entry = entry_attrs_.get(attr, [])
+ for regex in entry_attrs[attr]:
+ if regex in old_entry:
+ old_entry.remove(regex)
+ completed += 1
+ else:
+ failed['failed'][attr].append(regex)
+ entry_attrs[attr] = old_entry
+
+ entry_attrs = entry_to_dict(entry_attrs, **options)
+
+ # Set failed and completed to they can be harvested in the execute super
+ setattr(context, 'failed', failed)
+ setattr(context, 'completed', completed)
+ setattr(context, 'entry_attrs', entry_attrs)
+
+ # Make sure to returned the failed results if there is nothing to remove
+ if completed == 0:
+ ldap.get_entry(dn, attrs_list)
+ raise errors.EmptyModlist
+ return dn
+
+ def execute(self, *keys, **options):
+ __doc__ = _("""
+ Override this so we can set completed and failed.
+ """)
+ try:
+ result = super(automember_remove_condition, self).execute(*keys, **options)
+ except errors.EmptyModlist:
+ result = {'result': getattr(context, 'entry_attrs'), 'value': keys[-1]}
+ result['failed'] = getattr(context, 'failed')
+ result['completed'] = getattr(context, 'completed')
+ result['value'] = pkey_to_value(keys[-1], options)
+ return result
+
+
+@register()
+class automember_mod(LDAPUpdate):
+ __doc__ = _("""
+ Modify an automember rule.
+ """)
+ takes_args = automember_rule
+ takes_options = LDAPUpdate.takes_options + group_type
+ msg_summary = _('Modified automember rule "%(value)s"')
+
+ def execute(self, *keys, **options):
+ result = super(automember_mod, self).execute(*keys, **options)
+ result['value'] = pkey_to_value(keys[-1], options)
+ return result
+
+
+@register()
+class automember_del(LDAPDelete):
+ __doc__ = _("""
+ Delete an automember rule.
+ """)
+ takes_args = automember_rule
+ takes_options = group_type
+ msg_summary = _('Deleted automember rule "%(value)s"')
+
+ def execute(self, *keys, **options):
+ result = super(automember_del, self).execute(*keys, **options)
+ result['value'] = pkey_to_value([keys[-1]], options)
+ return result
+
+
+@register()
+class automember_find(LDAPSearch):
+ __doc__ = _("""
+ 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
+ )
+
+ def pre_callback(self, ldap, filters, attrs_list, base_dn, scope, *args, **options):
+ assert isinstance(base_dn, DN)
+ scope = ldap.SCOPE_SUBTREE
+ ndn = DN(('cn', options['type']), base_dn)
+ return (filters, ndn, scope)
+
+
+@register()
+class automember_show(LDAPRetrieve):
+ __doc__ = _("""
+ Display information about an automember rule.
+ """)
+ 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)
+ result['value'] = pkey_to_value(keys[-1], options)
+ return result
+
+
+@register()
+class automember_default_group_set(LDAPUpdate):
+ __doc__ = _("""
+ Set default (fallback) group for all unmatched entries.
+ """)
+
+ takes_options = (
+ Str('automemberdefaultgroup',
+ cli_name='default_group',
+ label=_('Default (fallback) Group'),
+ doc=_('Default (fallback) group for entries to land'),
+ flags=['no_create', 'no_update']
+ ),
+ ) + group_type
+ msg_summary = _('Set default (fallback) group for automember "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ dn = DN(('cn', options['type']), api.env.container_automember,
+ api.env.basedn)
+ entry_attrs['automemberdefaultgroup'] = self.obj.dn_exists(options['type'], options['automemberdefaultgroup'])
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_default_group_set, self).execute(*keys, **options)
+ result['value'] = pkey_to_value(options['type'], options)
+ return result
+
+
+@register()
+class automember_default_group_remove(LDAPUpdate):
+ __doc__ = _("""
+ Remove default (fallback) group for all unmatched entries.
+ """)
+
+ takes_options = group_type
+ msg_summary = _('Removed default (fallback) group for automember "%(value)s"')
+
+ def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+ dn = DN(('cn', options['type']), api.env.container_automember,
+ api.env.basedn)
+ attr = 'automemberdefaultgroup'
+
+ entry_attrs_ = ldap.get_entry(dn, [attr])
+
+ if attr not in entry_attrs_:
+ raise errors.NotFound(reason=_(u'No default (fallback) group set'))
+ else:
+ entry_attrs[attr] = []
+ return entry_attrs_.dn
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ assert isinstance(dn, DN)
+ if 'automemberdefaultgroup' not in entry_attrs:
+ entry_attrs['automemberdefaultgroup'] = unicode(_('No default (fallback) group set'))
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_default_group_remove, self).execute(*keys, **options)
+ result['value'] = pkey_to_value(options['type'], options)
+ return result
+
+
+@register()
+class automember_default_group_show(LDAPRetrieve):
+ __doc__ = _("""
+ Display information about the default (fallback) automember groups.
+ """)
+ takes_options = group_type
+
+ def pre_callback(self, ldap, dn, attrs_list, *keys, **options):
+ dn = DN(('cn', options['type']), api.env.container_automember,
+ api.env.basedn)
+ return dn
+
+ def post_callback(self, ldap, dn, entry_attrs, *keys, **options):
+ assert isinstance(dn, DN)
+ if 'automemberdefaultgroup' not in entry_attrs:
+ entry_attrs['automemberdefaultgroup'] = unicode(_('No default (fallback) group set'))
+ return dn
+
+ def execute(self, *keys, **options):
+ result = super(automember_default_group_show, self).execute(*keys, **options)
+ result['value'] = pkey_to_value(options['type'], options)
+ return result
+
+
+@register()
+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'),
+ ),
+ Flag(
+ 'no_wait?',
+ default=False,
+ label=_('No wait'),
+ doc=_("Don't wait for rebuilding membership"),
+ ),
+ )
+ has_output = output.standard_entry
+ has_output_params = (
+ DNParam(
+ 'dn',
+ label=_('Task DN'),
+ doc=_('DN of the started task'),
+ ),
+ )
+
+ 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:
+ try:
+ obj.get_dn_if_exists(name)
+ except errors.NotFound:
+ obj.handle_not_found(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
+
+ task_dn = DN(('cn', cn), REBUILD_TASK_CONTAINER)
+
+ entry = ldap.make_entry(
+ task_dn,
+ objectclass=['top', 'extensibleObject'],
+ cn=[cn],
+ basedn=[basedn],
+ filter=[search_filter],
+ scope=['sub'],
+ ttl=[3600])
+ ldap.add_entry(entry)
+
+ summary = _('Automember rebuild membership task started')
+ result = {'dn': task_dn}
+
+ if not options.get('no_wait'):
+ summary = _('Automember rebuild membership task completed')
+ result = {}
+ start_time = time.time()
+
+ while True:
+ try:
+ task = ldap.get_entry(task_dn)
+ except errors.NotFound:
+ break
+
+ if 'nstaskexitcode' in task:
+ if str(task.single_value['nstaskexitcode']) == '0':
+ summary=task.single_value['nstaskstatus']
+ break
+ else:
+ raise errors.DatabaseError(
+ desc=task.single_value['nstaskstatus'],
+ info=_("Task DN = '%s'" % task_dn))
+ time.sleep(1)
+ if time.time() > (start_time + 60):
+ raise errors.TaskTimeout(task=_('Automember'), task_dn=task_dn)
+
+ return dict(
+ result=result,
+ summary=unicode(summary),
+ value=pkey_to_value(None, options))