Repository: ansible/ansible

Issue:
gather_facts on shared freebsd vps target may fail to find dmesg
 - Bug Report

##### COMPONENT NAME
platform detection

##### ANSIBLE VERSION
```
ansible 2.3.1.0
  config file = 
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Dec 17 2016, 20:05:07) [GCC 6.4.0]
```

##### CONFIGURATION

##### OS / ENVIRONMENT

controller: Ubuntu 16.04
target: FreeBSD 11.1-RELEASE on https://www.nearlyfreespeech.net/

##### SUMMARY
I have a simple `playbook.yml` with just `ping` and `inventory.yml` with one host, which happens to be a FreeBSD host. When I do `ansible-playbook -i inventory.yml playbook.yml`, I get:
`... "cmd": "/sbin/dmesg", "failed": true, "msg": "[Errno 2] No such file or directory" ...`

work-around: add `gather_facts: false` to `playbook.yml`.

##### STEPS TO REPRODUCE
full details are in
https://gist.github.com/dckc/9872524e016eaf8f32ffe02d2961c5cf

##### EXPECTED RESULTS

ok=1

##### ACTUAL RESULTS

fail



Discussion:
It isn't the ping.  A play implicitly runs the `setup` module as part of the gather facts phase.  It is failing at that location before reaching the `ping` task.

`/sbin/dmesg` is called when `/var/run/dmesg.boot` is empty or missing.  We may need to add a graceful degradation there instead of hard failing.
discussed in irc, the target is a hosted image that is missing the `/sbin/dmesg` utility, this should still not be a fatal error when gathering facts, we should fix to warn/ignore the missing utilities and present only the possible facts.

Resolving patch:
diff --git a/lib/ansible/module_utils/facts/hardware/freebsd.py b/lib/ansible/module_utils/facts/hardware/freebsd.py
--- a/lib/ansible/module_utils/facts/hardware/freebsd.py
+++ b/lib/ansible/module_utils/facts/hardware/freebsd.py
@@ -66,12 +66,18 @@ def populate(self, collected_facts=None):
     def get_cpu_facts(self):
         cpu_facts = {}
         cpu_facts['processor'] = []
-        rc, out, err = self.module.run_command("/sbin/sysctl -n hw.ncpu")
-        cpu_facts['processor_count'] = out.strip()
+        sysctl = self.module.get_bin_path('sysctl')
+        if sysctl:
+            rc, out, err = self.module.run_command("%s -n hw.ncpu" % sysctl, check_rc=False)
+            cpu_facts['processor_count'] = out.strip()
 
         dmesg_boot = get_file_content(FreeBSDHardware.DMESG_BOOT)
         if not dmesg_boot:
-            rc, dmesg_boot, err = self.module.run_command("/sbin/dmesg")
+            try:
+                rc, dmesg_boot, err = self.module.run_command(self.module.get_bin_path("dmesg"), check_rc=False)
+            except Exception:
+                dmesg_boot = ''
+
         for line in dmesg_boot.splitlines():
             if 'CPU:' in line:
                 cpu = re.sub(r'CPU:\s+', r"", line)
@@ -84,29 +90,34 @@ def get_cpu_facts(self):
     def get_memory_facts(self):
         memory_facts = {}
 
-        rc, out, err = self.module.run_command("/sbin/sysctl vm.stats")
-        for line in out.splitlines():
-            data = line.split()
-            if 'vm.stats.vm.v_page_size' in line:
-                pagesize = int(data[1])
-            if 'vm.stats.vm.v_page_count' in line:
-                pagecount = int(data[1])
-            if 'vm.stats.vm.v_free_count' in line:
-                freecount = int(data[1])
-        memory_facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024
-        memory_facts['memfree_mb'] = pagesize * freecount // 1024 // 1024
-        # Get swapinfo.  swapinfo output looks like:
-        # Device          1M-blocks     Used    Avail Capacity
-        # /dev/ada0p3        314368        0   314368     0%
-        #
-        rc, out, err = self.module.run_command("/usr/sbin/swapinfo -k")
-        lines = out.splitlines()
-        if len(lines[-1]) == 0:
-            lines.pop()
-        data = lines[-1].split()
-        if data[0] != 'Device':
-            memory_facts['swaptotal_mb'] = int(data[1]) // 1024
-            memory_facts['swapfree_mb'] = int(data[3]) // 1024
+        sysctl = self.module.get_bin_path('sysctl')
+        if sysctl:
+            rc, out, err = self.module.run_command("%s vm.stats" % sysctl, check_rc=False)
+            for line in out.splitlines():
+                data = line.split()
+                if 'vm.stats.vm.v_page_size' in line:
+                    pagesize = int(data[1])
+                if 'vm.stats.vm.v_page_count' in line:
+                    pagecount = int(data[1])
+                if 'vm.stats.vm.v_free_count' in line:
+                    freecount = int(data[1])
+            memory_facts['memtotal_mb'] = pagesize * pagecount // 1024 // 1024
+            memory_facts['memfree_mb'] = pagesize * freecount // 1024 // 1024
+
+        swapinfo = self.module.get_bin_path('swapinfo')
+        if swapinfo:
+            # Get swapinfo.  swapinfo output looks like:
+            # Device          1M-blocks     Used    Avail Capacity
+            # /dev/ada0p3        314368        0   314368     0%
+            #
+            rc, out, err = self.module.run_command("%s -k" % swapinfo)
+            lines = out.splitlines()
+            if len(lines[-1]) == 0:
+                lines.pop()
+            data = lines[-1].split()
+            if data[0] != 'Device':
+                memory_facts['swaptotal_mb'] = int(data[1]) // 1024
+                memory_facts['swapfree_mb'] = int(data[3]) // 1024
 
         return memory_facts
 
diff --git a/lib/ansible/module_utils/facts/system/chroot.py b/lib/ansible/module_utils/facts/system/chroot.py
--- a/lib/ansible/module_utils/facts/system/chroot.py
+++ b/lib/ansible/module_utils/facts/system/chroot.py
@@ -20,7 +20,7 @@ def is_chroot():
             # check if my file system is the root one
             proc_root = os.stat('/proc/1/root/.')
             is_chroot = my_root.st_ino != proc_root.st_ino or my_root.st_dev != proc_root.st_dev
-        except:
+        except Exception:
             # I'm not root or no proc, fallback to checking it is inode #2
             is_chroot = (my_root.st_ino != 2)
 
diff --git a/lib/ansible/module_utils/facts/utils.py b/lib/ansible/module_utils/facts/utils.py
--- a/lib/ansible/module_utils/facts/utils.py
+++ b/lib/ansible/module_utils/facts/utils.py
@@ -29,7 +29,7 @@ def get_file_content(path, default=None, strip=True):
                     data = default
             finally:
                 datafile.close()
-        except:
+        except Exception:
             # ignore errors as some jails/containers might have readable permissions but not allow reads to proc
             # done in 2 blocks for 2.4 compat
             pass


Test patch: