Repository: ansible/ansible

Issue:
fact gathering sets `apt` as  package manager when apt is installed on Fedora 26
##### ISSUE TYPE
 - Bug Report
##### COMPONENT NAME
fact gathering
~package module~

##### ANSIBLE VERSION
```
ansible 2.4.2.0
  config file = /home/achatter/work/beaker-in-a-box/ansible.cfg
  configured module search path = [u'/home/achatter/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.14 (default, Nov  2 2017, 18:42:05) [GCC 7.2.1 20170915 (Red Hat 7.2.1-2)]

```

##### CONFIGURATION
<!---
DEFAULT_HOST_LIST(/home/achatter/work/beaker-in-a-box/ansible.cfg) = [u'/home/achatter/work/beaker-in-a-box/hosts']

##### OS / ENVIRONMENT
<!---
Fedora 26
```uname -r
4.14.4-200.fc26.x86_64
```

-->

##### SUMMARY
When apt is installed erroneously on a fedora distribution, the package module tries to run apt-get update to check for a present package 

##### STEPS TO REPRODUCE
1. install apt on your fedora machine
``` dnf install apt```
2. run this playbook
```yaml
- name: ensure local packages are installed for the playbook
  package:
    name: "{{ item }}"
    state: present 
  with_items:
    - libselinux-python
    -kc python-lxml
```

##### EXPECTED RESULTS
Use fedoras package manager (dnf) to check if package is present

##### ACTUAL RESULTS
Command failed with:
```
failed: [localhost] (item=libselinux-python) => {"changed": false, "cmd": "apt-get update", "item": "libselinux-python", "msg": "E: Repository model not found for http://download.fedoraproject.org/pub/ fedora/linux/development/rawhide/x86_64/os/", "rc": 100, "stderr": "E: Repository model not found for http://download.fedoraproject.org/pub/ fedora/linux/development/rawhide/x86_64/os/\n", "stderr_lines": ["E: Repository model not found for http://download.fedoraproject.org/pub/ fedora/linux/development/rawhide/x86_64/os/"], "stdout": "Ign http://download.fedoraproject.org fedora/linux/development/rawhide/x86_64/os/ repomd.xml\n", "stdout_lines": ["Ign http://download.fedoraproject.org fedora/linux/development/rawhide/x86_64/os/ repomd.xml"]}
failed: [localhost] (item=python-lxml) => {"changed": false, "cmd": "apt-get update", "item": "python-lxml", "msg": "E: Repository model not found for http://download.fedoraproject.org/pub/ fedora/linux/development/rawhide/x86_64/os/", "rc": 100, "stderr": "E: Repository model not found for http://download.fedoraproject.org/pub/ fedora/linux/development/rawhide/x86_64/os/\n", "stderr_lines": ["E: Repository model not found for http://download.fedoraproject.org/pub/ fedora/linux/development/rawhide/x86_64/os/"], "stdout": "Ign http://download.fedoraproject.org fedora/linux/development/rawhide/x86_64/os/ repomd.xml\n", "stdout_lines": ["Ign http://download.fedoraproject.org fedora/linux/development/rawhide/x86_64/os/ repomd.xml"]}

```



Discussion:
Files identified in the description:
* [lib/ansible/modules/packaging/os/package.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/packaging/os/package.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
The `use=` option allows you to force a package manager, this is not really as much a bug in `package` but in fact gathering detecting `apt` as the package manager.

cc @alikins 

Resolving patch:
diff --git a/lib/ansible/module_utils/facts/system/pkg_mgr.py b/lib/ansible/module_utils/facts/system/pkg_mgr.py
--- a/lib/ansible/module_utils/facts/system/pkg_mgr.py
+++ b/lib/ansible/module_utils/facts/system/pkg_mgr.py
@@ -68,6 +68,20 @@ class PkgMgrFactCollector(BaseFactCollector):
     name = 'pkg_mgr'
     _fact_ids = set()
     _platform = 'Generic'
+    required_facts = set(['distribution'])
+
+    def _check_rh_versions(self, collected_facts):
+        if collected_facts['ansible_distribution'] == 'Fedora':
+            try:
+                if int(collected_facts['ansible_distribution_major_version']) < 15:
+                    pkg_mgr_name = 'yum'
+                else:
+                    pkg_mgr_name = 'dnf'
+            except ValueError:
+                # If there's some new magical Fedora version in the future,
+                # just default to dnf
+                pkg_mgr_name = 'dnf'
+        return pkg_mgr_name
 
     def collect(self, module=None, collected_facts=None):
         facts_dict = {}
@@ -78,10 +92,21 @@ def collect(self, module=None, collected_facts=None):
             if os.path.exists(pkg['path']):
                 pkg_mgr_name = pkg['name']
 
-        if pkg_mgr_name == 'apt' and \
-                os.path.exists('/usr/bin/rpm') and \
-                not os.path.exists('/usr/bin/dpkg'):
-            pkg_mgr_name = 'apt_rpm'
+        # apt is easily installable and supported by distros other than those
+        # that are debian based, this handles some of those scenarios as they
+        # are reported/requested
+        if pkg_mgr_name == 'apt' and collected_facts['ansible_os_family'] in ["RedHat", "Altlinux"]:
+            if collected_facts['ansible_os_family'] == 'RedHat':
+                pkg_mgr_name = self._check_rh_versions(collected_facts)
+
+            elif collected_facts['ansible_os_family'] == 'Altlinux':
+                pkg_mgr_name = 'apt_rpm'
+
+        # pacman has become available by distros other than those that are Arch
+        # based by virtue of a dependency to the systemd mkosi project, this
+        # handles some of those scenarios as they are reported/requested
+        if pkg_mgr_name == 'pacman' and collected_facts['ansible_os_family'] in ["RedHat"]:
+            pkg_mgr_name = self._check_rh_versions(collected_facts)
 
         facts_dict['pkg_mgr'] = pkg_mgr_name
         return facts_dict


Test patch:
diff --git a/test/integration/targets/package/tasks/main.yml b/test/integration/targets/package/tasks/main.yml
--- a/test/integration/targets/package/tasks/main.yml
+++ b/test/integration/targets/package/tasks/main.yml
@@ -24,11 +24,33 @@
 - name: create our testing sub-directory
   file: path="{{ output_dir_test }}" state=directory
 
+# Verify correct default package manager for Fedora
+# Validates: https://github.com/ansible/ansible/issues/34014
+- block:
+    - name: install apt
+      dnf:
+        name: apt
+        state: present
+    - name: gather facts again
+      setup:
+    - name: validate output
+      assert:
+        that:
+          - 'ansible_pkg_mgr == "dnf"'
+  always:
+    - name: remove apt
+      dnf:
+        name: apt
+        state: absent
+    - name: gather facts again
+      setup:
+  when: ansible_distribution == "Fedora"
+
 ##
 ## package
 ##
 
-- name: define distros to attempt installing at on 
+- name: define distros to attempt installing at on
   set_fact:
     package_distros:
         - RedHat
diff --git a/test/units/module_utils/facts/test_ansible_collector.py b/test/units/module_utils/facts/test_ansible_collector.py
--- a/test/units/module_utils/facts/test_ansible_collector.py
+++ b/test/units/module_utils/facts/test_ansible_collector.py
@@ -202,6 +202,8 @@ class TestCollectedFacts(unittest.TestCase):
                       'env']
     not_expected_facts = ['facter', 'ohai']
 
+    collected_facts = {}
+
     def _mock_module(self, gather_subset=None):
         return mock_module(gather_subset=self.gather_subset)
 
@@ -212,7 +214,8 @@ def setUp(self):
         fact_collector = \
             ansible_collector.AnsibleFactCollector(collectors=collectors,
                                                    namespace=ns)
-        self.facts = fact_collector.collect(module=mock_module)
+        self.facts = fact_collector.collect(module=mock_module,
+                                            collected_facts=self.collected_facts)
 
     def _collectors(self, module,
                     all_collector_classes=None,
@@ -466,10 +469,15 @@ class TestOhaiCollectedFacts(TestCollectedFacts):
 class TestPkgMgrFacts(TestCollectedFacts):
     gather_subset = ['pkg_mgr']
     min_fact_count = 1
-    max_fact_count = 10
+    max_fact_count = 20
     expected_facts = ['gather_subset',
                       'module_setup',
                       'pkg_mgr']
+    collected_facts = {
+        "ansible_distribution": "Fedora",
+        "ansible_distribution_major_version": "28",
+        "ansible_os_family": "RedHat"
+    }
 
 
 class TestOpenBSDPkgMgrFacts(TestPkgMgrFacts):
diff --git a/test/units/module_utils/facts/test_collectors.py b/test/units/module_utils/facts/test_collectors.py
--- a/test/units/module_utils/facts/test_collectors.py
+++ b/test/units/module_utils/facts/test_collectors.py
@@ -224,6 +224,11 @@ class TestPkgMgrFacts(BaseFactsTest):
     valid_subsets = ['pkg_mgr']
     fact_namespace = 'ansible_pkgmgr'
     collector_class = PkgMgrFactCollector
+    collected_facts = {
+        "ansible_distribution": "Fedora",
+        "ansible_distribution_major_version": "28",
+        "ansible_os_family": "RedHat"
+    }
 
     def test_collect(self):
         module = self._mock_module()
@@ -233,6 +238,35 @@ def test_collect(self):
         self.assertIn('pkg_mgr', facts_dict)
 
 
+def _sanitize_os_path_apt_get(path):
+    if path == '/usr/bin/apt-get':
+        return True
+    else:
+        return False
+
+
+class TestPkgMgrFactsAptFedora(BaseFactsTest):
+    __test__ = True
+    gather_subset = ['!all', 'pkg_mgr']
+    valid_subsets = ['pkg_mgr']
+    fact_namespace = 'ansible_pkgmgr'
+    collector_class = PkgMgrFactCollector
+    collected_facts = {
+        "ansible_distribution": "Fedora",
+        "ansible_distribution_major_version": "28",
+        "ansible_os_family": "RedHat",
+        "ansible_pkg_mgr": "apt"
+    }
+
+    @patch('ansible.module_utils.facts.system.pkg_mgr.os.path.exists', side_effect=_sanitize_os_path_apt_get)
+    def test_collect(self, mock_os_path_exists):
+        module = self._mock_module()
+        fact_collector = self.collector_class()
+        facts_dict = fact_collector.collect(module=module, collected_facts=self.collected_facts)
+        self.assertIsInstance(facts_dict, dict)
+        self.assertIn('pkg_mgr', facts_dict)
+
+
 class TestOpenBSDPkgMgrFacts(BaseFactsTest):
     __test__ = True
     gather_subset = ['!all', 'pkg_mgr']