From 27a015f0ad79a33af2d7ed4d39635ca8f7c02fd8 Mon Sep 17 00:00:00 2001 From: Adrian Likins Date: Wed, 2 Aug 2017 11:04:01 -0400 Subject: [PATCH] add a 'min' type for gather_subset to collect nothing (#27085) previously gather_subset=['!all'] would still gather the min set of facts, and there was no way to collect no facts. The 'min' specifier in gather_subset is equilivent to exclude the minimal_gather_subset facts as well. gather_subset=['!all', '!min'] will collect no facts This also lets explicitly added gather_subsets override excludes. gather_subset=['pkg_mgr', '!all', '!min'] will collect only the pkg_mgr fact. --- lib/ansible/module_utils/facts/collector.py | 26 +++-- lib/ansible/modules/system/setup.py | 24 +++-- .../gathering_facts/test_gathering_facts.yml | 60 ++++++++++-- .../module_utils/facts/test_collector.py | 98 +++++++++++++++---- 4 files changed, 172 insertions(+), 36 deletions(-) diff --git a/lib/ansible/module_utils/facts/collector.py b/lib/ansible/module_utils/facts/collector.py index d55a9f0254b..4baf5bd3383 100644 --- a/lib/ansible/module_utils/facts/collector.py +++ b/lib/ansible/module_utils/facts/collector.py @@ -111,16 +111,31 @@ def get_collector_names(valid_subsets=None, additional_subsets = set() exclude_subsets = set() - for subset in gather_subset: - subset_id = subset + # total always starts with the min set, then + # adds of the additions in gather_subset, then + # excludes all of the excludes, then add any explicitly + # requested subsets. + gather_subset_with_min = ['min'] + gather_subset_with_min.extend(gather_subset) + + # subsets we mention in gather_subset explicitly, except for 'all'/'min' + explicitly_added = set() + for subset in gather_subset_with_min: + subset_id = subset + if subset_id == 'min': + additional_subsets.update(minimal_gather_subset) + continue if subset_id == 'all': additional_subsets.update(valid_subsets) continue if subset_id.startswith('!'): subset = subset[1:] + if subset == 'min': + exclude_subsets.update(minimal_gather_subset) + continue if subset == 'all': - exclude_subsets.update(valid_subsets) + exclude_subsets.update(valid_subsets - minimal_gather_subset) continue exclude = True else: @@ -137,14 +152,13 @@ def get_collector_names(valid_subsets=None, raise TypeError("Bad subset '%s' given to Ansible. gather_subset options allowed: all, %s" % (subset, ", ".join(sorted(valid_subsets)))) + explicitly_added.add(subset) additional_subsets.add(subset) if not additional_subsets: additional_subsets.update(valid_subsets) - additional_subsets.difference_update(exclude_subsets) - - additional_subsets.update(minimal_gather_subset) + additional_subsets.difference_update(exclude_subsets - explicitly_added) return additional_subsets diff --git a/lib/ansible/modules/system/setup.py b/lib/ansible/modules/system/setup.py index 346b557700c..3b2a5bc2c07 100644 --- a/lib/ansible/modules/system/setup.py +++ b/lib/ansible/modules/system/setup.py @@ -23,13 +23,16 @@ options: version_added: "2.1" description: - "if supplied, restrict the additional facts collected to the given subset. - Possible values: all, hardware, network, virtual, ohai, and + Possible values: all, min, hardware, network, virtual, ohai, and facter Can specify a list of values to specify a larger subset. Values can also be used with an initial C(!) to specify that that specific subset should not be collected. For instance: - !hardware, !network, !virtual, !ohai, !facter. Note that a few - facts are always collected. Use the filter parameter if you do - not want to display those." + !hardware, !network, !virtual, !ohai, !facter. If !all is specified + then only the min subset is collected. To avoid collecting even the + min subset, specify !all and !min subsets. To collect only specific facts, + use !all, !min, and specify the particular fact subsets. + Use the filter parameter if you do not want to display some collected + facts." required: false default: 'all' gather_timeout: @@ -93,18 +96,27 @@ EXAMPLES = """ # Display only facts returned by facter. # ansible all -m setup -a 'filter=facter_*' +# Collect only facts returned by facter. +# ansible all -m setup -a 'gather_subset=!all,!any,facter' + # Display only facts about certain interfaces. # ansible all -m setup -a 'filter=ansible_eth[0-2]' -# Restrict additional gathered facts to network and virtual. +# Restrict additional gathered facts to network and virtual (includes default minimum facts) # ansible all -m setup -a 'gather_subset=network,virtual' +# Collect only network and virtual (excludes default minimum facts) +# ansible all -m setup -a 'gather_subset=!all,!any,network,virtual' + # Do not call puppet facter or ohai even if present. # ansible all -m setup -a 'gather_subset=!facter,!ohai' -# Only collect the minimum amount of facts: +# Only collect the default minimum amount of facts: # ansible all -m setup -a 'gather_subset=!all' +# Collect no facts, even the default minimum subset of facts: +# ansible all -m setup -a 'gather_subset=!all,!min' + # Display facts from Windows hosts with custom facts stored in C(C:\\custom_facts). # ansible windows -m setup -a "fact_path='c:\\custom_facts'" """ diff --git a/test/integration/targets/gathering_facts/test_gathering_facts.yml b/test/integration/targets/gathering_facts/test_gathering_facts.yml index 6b3ed31d9ec..0e48693ddba 100644 --- a/test/integration/targets/gathering_facts/test_gathering_facts.yml +++ b/test/integration/targets/gathering_facts/test_gathering_facts.yml @@ -1,5 +1,4 @@ --- - - hosts: facthost7 tags: [ 'fact_negation' ] connection: local @@ -12,9 +11,6 @@ - "!hardware" register: not_hardware_facts - - name: debug setup with not hardware - debug: - var: not_hardware_facts - hosts: facthost0 tags: [ 'fact_min' ] @@ -67,6 +63,7 @@ - 'ansible_mounts|default("UNDEF_MOUNT") == "UNDEF_MOUNT"' - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' - 'ansible_env|default("UNDEF_ENV") == "UNDEF_ENV"' + - 'ansible_pkg_mgr|default("UNDEF_PKG_MGR") == "UNDEF_PKG_MGR"' - hosts: facthost11 tags: [ 'fact_min' ] @@ -111,7 +108,10 @@ - name: Test that only retrieving minimal facts work assert: that: + # from the min set, which should still collect - 'ansible_user_id|default("UNDEF_MIN") != "UNDEF_MIN"' + - 'ansible_env|default("UNDEF_ENV") != "UNDEF_ENV"' + # non min facts that are not collected - 'ansible_interfaces|default("UNDEF_NET") == "UNDEF_NET"' - 'ansible_mounts|default("UNDEF_HW") == "UNDEF_HW"' - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' @@ -198,10 +198,16 @@ - name: Test that negation of fact subsets work assert: that: + # network, not collected since it is not in min + - 'ansible_interfaces|default("UNDEF_NET") == "UNDEF_NET"' + # not collecting virt, should be undef + - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' + # mounts/devices are collected by hardware, so should be not collected and undef + - 'ansible_mounts|default("UNDEF_MOUNTS") == "UNDEF_MOUNTS"' + - 'ansible_devices|default("UNDEF_DEVICES") == "UNDEF_DEVICES"' + # from the min set, which should still collect - 'ansible_user_id|default("UNDEF_MIN") != "UNDEF_MIN"' - - 'ansible_interfaces|default("UNDEF_NET") != "UNDEF_NET"' - - 'ansible_mounts|default("UNDEF_HW") == "UNDEF_HW"' - - 'ansible_virtualization_role|default("UNDEF_VIRT") != "UNDEF_VIRT"' + - 'ansible_env|default("UNDEF_ENV") != "UNDEF_ENV"' - hosts: facthost8 tags: [ 'fact_mixed_negation_addition' ] @@ -217,6 +223,46 @@ - 'ansible_mounts|default("UNDEF_HW") == "UNDEF_HW"' - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' +- hosts: facthost14 + tags: [ 'fact_mixed_negation_addition_min' ] + connection: local + gather_subset: "!all,!min,network" + gather_facts: yes + tasks: + - name: Test that negation and additional subsets work together for min subset + assert: + that: + - 'ansible_user_id|default("UNDEF_MIN") == "UNDEF_MIN"' + - 'ansible_interfaces|default("UNDEF_NET") != "UNDEF_NET"' + - 'ansible_default_ipv4|default("UNDEF_DEFAULT_IPV4") != "UNDEF_DEFAULT_IPV4"' + - 'ansible_all_ipv4_addresses|default("UNDEF_ALL_IPV4") != "UNDEF_ALL_IPV4"' + - 'ansible_mounts|default("UNDEF_HW") == "UNDEF_HW"' + - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' + - 'ansible_env|default("UNDEF_ENV") == "UNDEF_ENV"' + +- hosts: facthost15 + tags: [ 'fact_negate_all_min_add_pkg_mgr' ] + connection: local + gather_subset: "!all,!min,pkg_mgr" + gather_facts: yes + tasks: + - name: Test that negation and additional subsets work together for min subset + assert: + that: + # network, not collected since it is not in min + - 'ansible_interfaces|default("UNDEF_NET") == "UNDEF_NET"' + # not collecting virt, should be undef + - 'ansible_virtualization_role|default("UNDEF_VIRT") == "UNDEF_VIRT"' + # mounts/devices are collected by hardware, so should be not collected and undef + - 'ansible_mounts|default("UNDEF_MOUNTS") == "UNDEF_MOUNTS"' + - 'ansible_devices|default("UNDEF_DEVICES") == "UNDEF_DEVICES"' + # from the min set, which should not collect + - 'ansible_user_id|default("UNDEF_MIN") == "UNDEF_MIN"' + - 'ansible_env|default("UNDEF_ENV") == "UNDEF_ENV"' + # the pkg_mgr fact we requested explicitly + - 'ansible_pkg_mgr|default("UNDEF_PKG_MGR") != "UNDEF_PKGMGR"' + + - hosts: facthost9 tags: [ 'fact_local'] connection: local diff --git a/test/units/module_utils/facts/test_collector.py b/test/units/module_utils/facts/test_collector.py index 9cc48b5a26f..362a6ffcfe7 100644 --- a/test/units/module_utils/facts/test_collector.py +++ b/test/units/module_utils/facts/test_collector.py @@ -37,14 +37,14 @@ class TestGetCollectorNames(unittest.TestCase): def test_empty_sets(self): res = collector.get_collector_names(valid_subsets=frozenset([]), minimal_gather_subset=frozenset([]), - gather_subset=set([])) + gather_subset=[]) self.assertIsInstance(res, set) self.assertEqual(res, set([])) def test_empty_valid_and_min_with_all_gather_subset(self): res = collector.get_collector_names(valid_subsets=frozenset([]), minimal_gather_subset=frozenset([]), - gather_subset=set(['all'])) + gather_subset=['all']) self.assertIsInstance(res, set) self.assertEqual(res, set([])) @@ -52,10 +52,48 @@ class TestGetCollectorNames(unittest.TestCase): valid_subsets = frozenset(['my_fact']) res = collector.get_collector_names(valid_subsets=valid_subsets, minimal_gather_subset=frozenset([]), - gather_subset=set(['all'])) + gather_subset=['all']) self.assertIsInstance(res, set) self.assertEqual(res, set(['my_fact'])) + def _compare_res(self, gather_subset1, gather_subset2, + valid_subsets=None, min_subset=None): + + valid_subsets = valid_subsets or frozenset() + minimal_gather_subset = min_subset or frozenset() + + res1 = collector.get_collector_names(valid_subsets=valid_subsets, + minimal_gather_subset=minimal_gather_subset, + gather_subset=gather_subset1) + + res2 = collector.get_collector_names(valid_subsets=valid_subsets, + minimal_gather_subset=minimal_gather_subset, + gather_subset=gather_subset2) + + return res1, res2 + + def test_not_all_other_order(self): + valid_subsets = frozenset(['min_fact', 'something_else', 'whatever']) + minimal_gather_subset = frozenset(['min_fact']) + + res1, res2 = self._compare_res(['!all', 'whatever'], + ['whatever', '!all'], + valid_subsets=valid_subsets, + min_subset=minimal_gather_subset) + self.assertEqual(res1, res2) + self.assertEqual(res1, set(['min_fact', 'whatever'])) + + def test_not_all_other_order_min(self): + valid_subsets = frozenset(['min_fact', 'something_else', 'whatever']) + minimal_gather_subset = frozenset(['min_fact']) + + res1, res2 = self._compare_res(['!min_fact', 'whatever'], + ['whatever', '!min_fact'], + valid_subsets=valid_subsets, + min_subset=minimal_gather_subset) + self.assertEqual(res1, res2) + self.assertEqual(res1, set(['whatever'])) + def test_one_minimal_with_all_gather_subset(self): my_fact = 'my_fact' valid_subsets = frozenset([my_fact]) @@ -63,7 +101,7 @@ class TestGetCollectorNames(unittest.TestCase): res = collector.get_collector_names(valid_subsets=valid_subsets, minimal_gather_subset=minimal_gather_subset, - gather_subset=set(['all'])) + gather_subset=['all']) self.assertIsInstance(res, set) self.assertEqual(res, set(['my_fact'])) @@ -74,7 +112,7 @@ class TestGetCollectorNames(unittest.TestCase): # even with '!all', the minimal_gather_subset should be returned res = collector.get_collector_names(valid_subsets=valid_subsets, minimal_gather_subset=minimal_gather_subset, - gather_subset=set(['all'])) + gather_subset=['all']) self.assertIsInstance(res, set) self.assertEqual(res, set(['my_fact', 'something_else', 'whatever'])) @@ -85,21 +123,23 @@ class TestGetCollectorNames(unittest.TestCase): # even with '!all', the minimal_gather_subset should be returned res = collector.get_collector_names(valid_subsets=valid_subsets, minimal_gather_subset=minimal_gather_subset, - gather_subset=set(['!all'])) + gather_subset=['!all']) self.assertIsInstance(res, set) self.assertEqual(res, set(['my_fact'])) def test_gather_subset_excludes(self): valid_subsets = frozenset(['my_fact', 'something_else', 'whatever']) - minimal_gather_subset = frozenset(['my_fact']) + minimal_gather_subset = frozenset(['min_fact', 'min_another']) # even with '!all', the minimal_gather_subset should be returned res = collector.get_collector_names(valid_subsets=valid_subsets, minimal_gather_subset=minimal_gather_subset, - gather_subset=set(['all', '!my_fact', '!whatever'])) + # gather_subset=set(['all', '!my_fact', '!whatever'])) + # gather_subset=['all', '!my_fact', '!whatever']) + gather_subset=['!min_fact', '!whatever']) self.assertIsInstance(res, set) - # my_facts is in minimal_gather_subset, so always returned - self.assertEqual(res, set(['my_fact', 'something_else'])) + # min_another is in minimal_gather_subset, so always returned + self.assertEqual(res, set(['min_another'])) def test_gather_subset_excludes_ordering(self): valid_subsets = frozenset(['my_fact', 'something_else', 'whatever']) @@ -107,11 +147,35 @@ class TestGetCollectorNames(unittest.TestCase): res = collector.get_collector_names(valid_subsets=valid_subsets, minimal_gather_subset=minimal_gather_subset, - gather_subset=set(['!all', 'whatever'])) + gather_subset=['!all', 'whatever']) self.assertIsInstance(res, set) # excludes are higher precedence than includes, so !all excludes everything # and then minimal_gather_subset is added. so '!all', 'other' == '!all' - self.assertEqual(res, set(['my_fact'])) + self.assertEqual(res, set(['my_fact', 'whatever'])) + + def test_gather_subset_excludes_min(self): + valid_subsets = frozenset(['min_fact', 'something_else', 'whatever']) + minimal_gather_subset = frozenset(['min_fact']) + + res = collector.get_collector_names(valid_subsets=valid_subsets, + minimal_gather_subset=minimal_gather_subset, + gather_subset=['whatever', '!min']) + self.assertIsInstance(res, set) + # excludes are higher precedence than includes, so !all excludes everything + # and then minimal_gather_subset is added. so '!all', 'other' == '!all' + self.assertEqual(res, set(['whatever'])) + + def test_gather_subset_excludes_min_and_all(self): + valid_subsets = frozenset(['min_fact', 'something_else', 'whatever']) + minimal_gather_subset = frozenset(['min_fact']) + + res = collector.get_collector_names(valid_subsets=valid_subsets, + minimal_gather_subset=minimal_gather_subset, + gather_subset=['whatever', '!all', '!min']) + self.assertIsInstance(res, set) + # excludes are higher precedence than includes, so !all excludes everything + # and then minimal_gather_subset is added. so '!all', 'other' == '!all' + self.assertEqual(res, set(['whatever'])) def test_invaid_gather_subset(self): valid_subsets = frozenset(['my_fact', 'something_else']) @@ -122,7 +186,7 @@ class TestGetCollectorNames(unittest.TestCase): collector.get_collector_names, valid_subsets=valid_subsets, minimal_gather_subset=minimal_gather_subset, - gather_subset=set(['my_fact', 'not_a_valid_gather_subset'])) + gather_subset=['my_fact', 'not_a_valid_gather_subset']) class TestCollectorClassesFromGatherSubset(unittest.TestCase): @@ -145,13 +209,13 @@ class TestCollectorClassesFromGatherSubset(unittest.TestCase): def test(self): res = self._classes(all_collector_classes=default_collectors.collectors, - gather_subset=set(['!all'])) + gather_subset=['!all']) self.assertIsInstance(res, list) self.assertEqual(res, []) def test_env(self): res = self._classes(all_collector_classes=default_collectors.collectors, - gather_subset=set(['env'])) + gather_subset=['env']) self.assertIsInstance(res, list) self.assertEqual(res, [default_collectors.EnvFactCollector]) @@ -181,7 +245,7 @@ class TestCollectorClassesFromGatherSubset(unittest.TestCase): def test_collector_specified_multiple_times(self): res = self._classes(all_collector_classes=default_collectors.collectors, - gather_subset=set(['platform', 'all', 'machine'])) + gather_subset=['platform', 'all', 'machine']) self.assertIsInstance(res, list) self.assertIn(default_collectors.PlatformFactCollector, res) @@ -193,4 +257,4 @@ class TestCollectorClassesFromGatherSubset(unittest.TestCase): 'Bad subset.*unknown_collector.*given to Ansible.*allowed\:.*all,.*env.*', self._classes, all_collector_classes=default_collectors.collectors, - gather_subset=set(['env', 'unknown_collector'])) + gather_subset=['env', 'unknown_collector'])