From 5fc7ad6878c6483b4246c6f9b3fcc7ecebe3cead Mon Sep 17 00:00:00 2001
From: Chris Coley <chris@codingallnight.com>
Date: Wed, 4 Jul 2018 14:11:44 -0700
Subject: [PATCH] Update the bootstrap tasks to fail if Python is not available

Initially, this role would check if Python was available in the default location
of '/usr/bin/python', and would install Python 2 if it wasn't. I didn't like the
idea of installing Python 2 so I updated the role to look for a usable Python
interpreter in other standard locations, including Python 3's default location
of '/usr/bin/python3'. Then it would tell the user to set
'ansible_python_interpreter' to one of the interpreters it found. I liked this
because it gave the user the choice of how to resolve the issue. However, this
role was running all the Python finding/checking tasks for every host and that
struck me as inefficient, so I changed it.

Now, the role checks if the host is pingable. If it is not, then it runs tasks
to determine why that specific host is not pingable. It then relays that
information to the user by failing out with the fail module and a descriptive
message. Hosts that are pingable do not run these tasks.
---
 defaults/main.yml                |  7 ----
 tasks/bootstrap.yml              | 67 +++++---------------------------
 tasks/diagnose-unpingable.yml    | 32 +++++++++++++++
 tasks/find-python-executable.yml | 21 ++++++++++
 tasks/test-python-executable.yml | 22 +++++++++++
 5 files changed, 85 insertions(+), 64 deletions(-)
 create mode 100644 tasks/diagnose-unpingable.yml
 create mode 100644 tasks/find-python-executable.yml
 create mode 100644 tasks/test-python-executable.yml

diff --git a/defaults/main.yml b/defaults/main.yml
index 23329f3..99f43ab 100644
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -15,12 +15,5 @@ common_utils:
   - screen
   - unzip
   - vim
-
-# The path to the python executable to use
-# If set and the path is executable, then it will be used.
-# Else if Python 2 is executable, use it.
-# Else if Python 3 is executable, use if.
-# Else, fail.
-ansible_python_interpreter:
 ...
 # vi: set ts=2 sts=2 sw=2 et ft=yaml: 
diff --git a/tasks/bootstrap.yml b/tasks/bootstrap.yml
index dec5621..fcbba72 100644
--- a/tasks/bootstrap.yml
+++ b/tasks/bootstrap.yml
@@ -1,65 +1,17 @@
 ---
-- name: Bootstrap the host by selecting which Python interpreter to use, and other OS specific stuff
+- name: Bootstrap the host by making sure the host is pingable and installing OS specific requirements
   tags: bootstrap
   block:
-    # If ansible_python_interpreter is set and exists, use it.
-    # Elif python 2 exists, use it.
-    # Elif python 3 exists, use it.
-    # Else, fail.
-    - name: Figure out which version of Python to use
-      when: ansible_python_interpreter is not defined or not ansible_python_interpreter
-      block:
-        - name: Check if /usr/bin/python is executable
-          raw: 'test -x /usr/bin/python'
-          changed_when: false
-          failed_when: false
-          register: python_default
-        - name: Set 'ansible_python_interpreter' to /usr/bin/python
-          when: python_default.rc is defined and python_default.rc == 0
-          set_fact:
-            ansible_python_interpreter: /usr/bin/python
-
-        - name: Check if /usr/bin/python2 is executable
-          raw: 'test -x /usr/bin/python2'
-          changed_when: false
-          failed_when: false
-          register: python_2
-        - name: Set 'ansible_python_interpreter' to /usr/bin/python2
-          when: python_2.rc is defined and python_2.rc == 0
-          set_fact:
-            ansible_python_interpreter: /usr/bin/python2
-
-        - name: Check if /usr/bin/python3 is executable
-          raw: 'test -x /usr/bin/python3'
-          changed_when: false
-          failed_when: false
-          register: python_3
-        - name: Set 'ansible_python_interpreter' to /usr/bin/python3
-          when: python_3.rc is defined and python_3.rc == 0
-          set_fact:
-            ansible_python_interpreter: /usr/bin/python3
-
-#    - debug:
-#        var: ansible_python_interpreter
-
-    - name: Check if 'ansible_python_interpreter' is executable
-      raw: 'test -x {{ ansible_python_interpreter }}'
-      changed_when: false
+    - name: Check if the host is pingable
+      ping:
+      register: _can_ping
       failed_when: false
-      register: python_executable
-    - name: Assert that 'ansible_python_interpreter' is executable
-      assert:
-        that:
-          - ansible_python_interpreter is defined and ansible_python_interpreter
-          - python_executable.rc is defined and python_executable.rc == 0
 
-#    - name: Install Python
-#      raw: >
-#        (test -e /etc/redhat-release && yum install -y python)
-#        || (test -e /etc/debian_version && apt-get -y update && apt-get install -y python)
-#      when: result.rc is defined and result.rc == 1
+    - name: If the host is not pingable, find out why
+      include_tasks: tasks/diagnose-unpingable.yml
+      when: _can_ping.ping is not defined or _can_ping.ping != 'pong'
 
-    - name: Gather facts to determine host OS
+    - name: Gather minimal facts to determine host OS
       setup:
         gather_subset: min
 
@@ -74,7 +26,8 @@
             - vars/{{ ansible_os_family | lower }}.yml
           skip: true
 
-    - include_tasks: '{{ item }}'
+    - name: Include OS specific bootstrap tasks
+      include_tasks: '{{ item }}'
       with_first_found:
         - tasks/bootstrap_{{ ansible_distribution | lower }}-{{ ansible_distribution_version | lower }}.yml
         - tasks/bootstrap_{{ ansible_distribution | lower }}-{{ ansible_distribution_release | lower }}.yml
diff --git a/tasks/diagnose-unpingable.yml b/tasks/diagnose-unpingable.yml
new file mode 100644
index 0000000..960854b
--- /dev/null
+++ b/tasks/diagnose-unpingable.yml
@@ -0,0 +1,32 @@
+#
+# This task list attempts to diagnose why a host is not pingable with Ansible's
+# built-in ping module. It is currently able to diagnose issues with Python
+# being unavailable, or Python being an unsupported version.
+#
+---
+# If the error has a defined error message and is not related to Python not
+# being found, then display that message
+- fail:
+    msg: '{{ _can_ping.msg }}'
+  when: _can_ping.msg is defined and (_can_ping.rc is not defined or _can_ping.rc != 127)
+
+# If the error is related to Python not being found, then try to find the
+# available Python interpreters
+- name: Python is not found
+  when: _can_ping.rc is defined and _can_ping.rc == 127
+  block:
+    - include_tasks: tasks/find-python-executable.yml
+    - fail:
+        msg: "Unable to find Python on {{ inventory_hostname }}.\n
+              Please set 'ansible_python_interpreter' to an executable Python interpreter.\n
+              The host has these available: ( {{ _python_interpreters | join(' , ') }} )"
+
+# This block is a catchall that runs whenever an unanticipated error occurs
+- name: Something Unexpected Happened
+  block:
+    - debug:
+        var: _can_ping
+    - fail:
+        msg: Something Unexpected Happened
+...
+# vi: set ts=2 sts=2 sw=2 et ft=yaml:
diff --git a/tasks/find-python-executable.yml b/tasks/find-python-executable.yml
new file mode 100644
index 0000000..08b84f3
--- /dev/null
+++ b/tasks/find-python-executable.yml
@@ -0,0 +1,21 @@
+#
+# This task list atempts to find all the available Python interpreters on a host
+# and build a list of them in a variable called '_python_interpreters'.
+#
+---
+- block:
+    # Initialize an empty list of available interpreters
+    - set_fact:
+        _python_interpreters: []
+
+    # Loop through the common interpreter paths, testing if they are available
+    - name: Find a usable Python interpreter
+      include_tasks: tasks/test-python-executable.yml
+      loop_control:
+        loop_var: _python_path
+      with_items:
+        - /usr/bin/python
+        - /usr/bin/python2
+        - /usr/bin/python3
+...
+# vi: set ts=2 sts=2 sw=2 et ft=yaml:
diff --git a/tasks/test-python-executable.yml b/tasks/test-python-executable.yml
new file mode 100644
index 0000000..2a219d5
--- /dev/null
+++ b/tasks/test-python-executable.yml
@@ -0,0 +1,22 @@
+#
+# This task list tests is a python interpreter path is executable, and appends
+# it to a list of executable paths if it is. Ansible has version requirements
+# for Python interpreters, but this task list doesn't check for those.
+#
+# Parameters:
+#   _python_path          An absolute path to a Python interpreter.
+#   _python_interpreters  A list to append executable paths to.
+#
+---
+- name: Check if '{{ _python_path }}' is executable
+  raw: 'test -x {{ _python_path }}'
+  changed_when: false
+  failed_when: false
+  register: __can_exec
+
+- name: If '{{ _python_path }}' is executable, append it to the list of available interpreters
+  set_fact:
+    _python_interpreters: "{{ _python_interpreters }} + [ '{{ _python_path }}' ]"
+  when: __can_exec.rc is defined and __can_exec.rc == 0
+...
+# vi: set ts=2 sts=2 sw=2 et ft=yaml:
-- 
GitLab