From 67875c3b75ad1af493ff5930f9c5fd5e9797b775 Mon Sep 17 00:00:00 2001 From: Thomas Woerner Date: Oct 12 2018 07:50:29 +0000 Subject: Find orphan automember rules If groups or hostgroups have been removed after automember rules have been created using them, then automember-rebuild, automember-add, host-add and more commands could fail. A new command has been added to the ipa tool: ipa automember-find-orphans --type={hostgroup,group} [--remove] This command retuns the list of orphan automember rules in the same way as automember-find. With the --remove option the orphan rules are also removed. The IPA API version has been increased and a test case has been added. Using ideas from a patch by: Rob Crittenden See: https://pagure.io/freeipa/issue/6476 Signed-off-by: Thomas Woerner Reviewed-By: Christian Heimes Reviewed-By: Florence Blanc-Renaud Reviewed-By: Florence Blanc-Renaud --- diff --git a/API.txt b/API.txt index 49216cb..93e1a38 100644 --- a/API.txt +++ b/API.txt @@ -186,6 +186,20 @@ output: Output('count', type=[]) output: ListOfEntries('result') output: Output('summary', type=[, ]) output: Output('truncated', type=[]) +command: automember_find_orphans/1 +args: 1,7,4 +arg: Str('criteria?') +option: Flag('all', autofill=True, cli_name='all', default=False) +option: Str('description?', autofill=False, cli_name='desc') +option: Flag('pkey_only?', autofill=True, default=False) +option: Flag('raw', autofill=True, cli_name='raw', default=False) +option: Flag('remove?', autofill=True, default=False) +option: StrEnum('type', values=[u'group', u'hostgroup']) +option: Str('version?') +output: Output('count', type=[]) +output: ListOfEntries('result') +output: Output('summary', type=[, ]) +output: Output('truncated', type=[]) command: automember_mod/1 args: 1,9,3 arg: Str('cn', cli_name='automember_rule') @@ -6503,6 +6517,7 @@ default: automember_default_group_set/1 default: automember_default_group_show/1 default: automember_del/1 default: automember_find/1 +default: automember_find_orphans/1 default: automember_mod/1 default: automember_rebuild/1 default: automember_remove_condition/1 diff --git a/VERSION.m4 b/VERSION.m4 index f437ef0..9d5532c 100644 --- a/VERSION.m4 +++ b/VERSION.m4 @@ -83,8 +83,8 @@ define(IPA_DATA_VERSION, 20100614120000) # # ######################################################## define(IPA_API_VERSION_MAJOR, 2) -define(IPA_API_VERSION_MINOR, 229) -# Last change: Added the Certificate parameter +define(IPA_API_VERSION_MINOR, 230) +# Last change: Added `automember-find-orphans' command ######################################################## diff --git a/ipaserver/plugins/automember.py b/ipaserver/plugins/automember.py index a502aea..a7f468d 100644 --- a/ipaserver/plugins/automember.py +++ b/ipaserver/plugins/automember.py @@ -117,6 +117,11 @@ EXAMPLES: Find all of the automember rules: ipa automember-find """) + _(""" + Find all of the orphan automember rules: + ipa automember-find-orphans --type=hostgroup + Find all of the orphan automember rules and remove them: + ipa automember-find-orphans --type=hostgroup --remove +""") + _(""" Display a automember rule: ipa automember-show --type=hostgroup webservers ipa automember-show --type=group devel @@ -817,3 +822,58 @@ class automember_rebuild(Method): result=result, summary=unicode(summary), value=pkey_to_value(None, options)) + + +@register() +class automember_find_orphans(LDAPSearch): + __doc__ = _(""" + Search for orphan automember rules. The command might need to be run as + a privileged user user to get all orphan rules. + """) + takes_options = group_type + ( + Flag( + 'remove?', + doc=_("Remove orphan automember rules"), + ), + ) + + msg_summary = ngettext( + '%(count)d rules matched', '%(count)d rules matched', 0 + ) + + def execute(self, *keys, **options): + results = super(automember_find_orphans, self).execute(*keys, + **options) + + remove_option = options.get('remove') + pkey_only = options.get('pkey_only', False) + ldap = self.obj.backend + orphans = [] + for entry in results["result"]: + am_dn_entry = entry['automembertargetgroup'][0] + # Make DN for --raw option + if not isinstance(am_dn_entry, DN): + am_dn_entry = DN(am_dn_entry) + try: + ldap.get_entry(am_dn_entry) + except errors.NotFound: + if pkey_only: + # For pkey_only remove automembertargetgroup + del(entry['automembertargetgroup']) + orphans.append(entry) + if remove_option: + ldap.delete_entry(entry['dn']) + + results["result"][:] = orphans + results["count"] = len(orphans) + return results + + 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) + if options.get('pkey_only', False): + # For pkey_only add automembertargetgroup + attrs_list.append('automembertargetgroup') + return filters, ndn, scope diff --git a/ipatests/test_xmlrpc/test_automember_plugin.py b/ipatests/test_xmlrpc/test_automember_plugin.py index ffbc911..c83e11a 100644 --- a/ipatests/test_xmlrpc/test_automember_plugin.py +++ b/ipatests/test_xmlrpc/test_automember_plugin.py @@ -715,3 +715,51 @@ class TestMultipleAutomemberConditions(XMLRPC_test): defaultgroup1.ensure_missing() defaulthostgroup1.ensure_missing() + + +@pytest.mark.tier1 +class TestAutomemberFindOrphans(XMLRPC_test): + def test_create_deps_for_find_orphans(self, hostgroup1, host1, + automember_hostgroup): + """ Create host, hostgroup, and automember tracker for this class + of tests. """ + + # Create hostgroup1 and automember rule with condition + hostgroup1.ensure_exists() + host1.ensure_exists() + + # Manually create automember rule and condition, racker will try to + # remove the automember rule in the end, which is failing as the rule + # is already removed + api.Command['automember_add'](hostgroup1.cn, type=u'hostgroup') + api.Command['automember_add_condition']( + hostgroup1.cn, + key=u'fqdn', type=u'hostgroup', + automemberinclusiveregex=[hostgroup_include_regex] + ) + + hostgroup1.retrieve() + + def test_find_orphan_automember_rules(self, hostgroup1): + """ Remove hostgroup1, find and remove obsolete automember rules. """ + # Remove hostgroup1 + + hostgroup1.ensure_missing() + + # Find obsolete automember rules + result = api.Command['automember_find_orphans'](type=u'hostgroup') + assert result['count'] == 1 + + # Find and remove obsolete automember rules + result = api.Command['automember_find_orphans'](type=u'hostgroup', + remove=True) + assert result['count'] == 1 + + # Find obsolete automember rules + result = api.Command['automember_find_orphans'](type=u'hostgroup') + assert result['count'] == 0 + + # Final cleanup of automember rule if it still exists + with raises_exact(errors.NotFound( + reason=u'%s: Automember rule not found' % hostgroup1.cn)): + api.Command['automember_del'](hostgroup1.cn, type=u'hostgroup')