From 4b8328fe94015b25209d9b3e6c94b2a3cd0cff40 Mon Sep 17 00:00:00 2001
From: Colin Walters <walters@verbum.org>
Date: Sat, 18 Feb 2017 10:21:56 -0500
Subject: roles/lib_openshift: Handle /usr/local/bin/oc with sudo

The real changes here are in `src/lib/{base,import}.py` remember, the rest is
committed to git rather than built for some reason.

This is tested on CentOS AH (python2), I haven't yet tested the Python 3 path
here. I tried the suggestion in the PR for using Ansible's `module` but AFAICS
that would require passing down the `module` variable pretty far down into this
code. This implementation seems OK too.

Closes: https://github.com/openshift/openshift-ansible/issues/3410
---
 roles/lib_openshift/library/oadm_manage_node.py      | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_edit.py               | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_env.py                | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_label.py              | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_obj.py                | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_process.py            | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_route.py              | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_scale.py              | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_secret.py             | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_service.py            | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_serviceaccount.py     | 20 ++++++++++++++++----
 .../library/oc_serviceaccount_secret.py              | 20 ++++++++++++++++----
 roles/lib_openshift/library/oc_version.py            | 20 ++++++++++++++++----
 roles/lib_openshift/src/lib/base.py                  | 19 +++++++++++++++----
 roles/lib_openshift/src/lib/import.py                |  1 +
 15 files changed, 224 insertions(+), 56 deletions(-)

(limited to 'roles/lib_openshift')

diff --git a/roles/lib_openshift/library/oadm_manage_node.py b/roles/lib_openshift/library/oadm_manage_node.py
index 0ac233c72..840882bbc 100644
--- a/roles/lib_openshift/library/oadm_manage_node.py
+++ b/roles/lib_openshift/library/oadm_manage_node.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -963,10 +964,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -982,7 +991,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_edit.py b/roles/lib_openshift/library/oc_edit.py
index 1b1faf496..9d174ba31 100644
--- a/roles/lib_openshift/library/oc_edit.py
+++ b/roles/lib_openshift/library/oc_edit.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -991,10 +992,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -1010,7 +1019,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_env.py b/roles/lib_openshift/library/oc_env.py
index 1426565b4..d32773c38 100644
--- a/roles/lib_openshift/library/oc_env.py
+++ b/roles/lib_openshift/library/oc_env.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -958,10 +959,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -977,7 +986,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_label.py b/roles/lib_openshift/library/oc_label.py
index 0db8585a4..0f98728f8 100644
--- a/roles/lib_openshift/library/oc_label.py
+++ b/roles/lib_openshift/library/oc_label.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -967,10 +968,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -986,7 +995,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_obj.py b/roles/lib_openshift/library/oc_obj.py
index 6d0b391b9..39da0ed5c 100644
--- a/roles/lib_openshift/library/oc_obj.py
+++ b/roles/lib_openshift/library/oc_obj.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -970,10 +971,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -989,7 +998,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_process.py b/roles/lib_openshift/library/oc_process.py
index a1ee79f6e..d3b2de4dc 100644
--- a/roles/lib_openshift/library/oc_process.py
+++ b/roles/lib_openshift/library/oc_process.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -959,10 +960,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -978,7 +987,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_route.py b/roles/lib_openshift/library/oc_route.py
index 4b5c4460c..d8d5e1891 100644
--- a/roles/lib_openshift/library/oc_route.py
+++ b/roles/lib_openshift/library/oc_route.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -1001,10 +1002,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -1020,7 +1029,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_scale.py b/roles/lib_openshift/library/oc_scale.py
index c73e96e10..e2a004f54 100644
--- a/roles/lib_openshift/library/oc_scale.py
+++ b/roles/lib_openshift/library/oc_scale.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -945,10 +946,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -964,7 +973,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_secret.py b/roles/lib_openshift/library/oc_secret.py
index 6ab5e81b2..23d61a4fb 100644
--- a/roles/lib_openshift/library/oc_secret.py
+++ b/roles/lib_openshift/library/oc_secret.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -991,10 +992,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -1010,7 +1019,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_service.py b/roles/lib_openshift/library/oc_service.py
index 319ec4bd7..6e7b1b52e 100644
--- a/roles/lib_openshift/library/oc_service.py
+++ b/roles/lib_openshift/library/oc_service.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -997,10 +998,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -1016,7 +1025,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_serviceaccount.py b/roles/lib_openshift/library/oc_serviceaccount.py
index 7104355f0..59dee8efb 100644
--- a/roles/lib_openshift/library/oc_serviceaccount.py
+++ b/roles/lib_openshift/library/oc_serviceaccount.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -943,10 +944,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -962,7 +971,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_serviceaccount_secret.py b/roles/lib_openshift/library/oc_serviceaccount_secret.py
index 3a9380661..906134797 100644
--- a/roles/lib_openshift/library/oc_serviceaccount_secret.py
+++ b/roles/lib_openshift/library/oc_serviceaccount_secret.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -943,10 +944,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -962,7 +971,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_version.py b/roles/lib_openshift/library/oc_version.py
index 9b660e1d3..eeb28e7fc 100644
--- a/roles/lib_openshift/library/oc_version.py
+++ b/roles/lib_openshift/library/oc_version.py
@@ -36,6 +36,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
@@ -915,10 +916,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -934,7 +943,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/src/lib/base.py b/roles/lib_openshift/src/lib/base.py
index 9cad5e667..0018a6710 100644
--- a/roles/lib_openshift/src/lib/base.py
+++ b/roles/lib_openshift/src/lib/base.py
@@ -224,10 +224,18 @@ class OpenShiftCLI(object):
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
         cmds = []
-        if oadm:
-            cmds = ['oadm']
+        basecmd = 'oadm' if oadm else 'oc'
+        # https://github.com/openshift/openshift-ansible/issues/3410
+        # oc can be in /usr/local/bin in some cases, but that may not
+        # be in $PATH due to ansible/sudo
+        if sys.version_info[0] == 3:
+            basepath = shutil.which(basecmd) # pylint: disable=no-member
         else:
-            cmds = ['oc']
+            import distutils.spawn
+            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
+        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
+            basecmd = '/usr/local/bin/' + basecmd
+        cmds.append(basecmd)
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -243,7 +251,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/src/lib/import.py b/roles/lib_openshift/src/lib/import.py
index a79297898..5b2479b87 100644
--- a/roles/lib_openshift/src/lib/import.py
+++ b/roles/lib_openshift/src/lib/import.py
@@ -10,6 +10,7 @@ import atexit
 import copy
 import json
 import os
+import sys
 import re
 import shutil
 import subprocess
-- 
cgit v1.2.3


From a3603a5187839e25e9d8a51de70fea0b68439839 Mon Sep 17 00:00:00 2001
From: Jason DeTiberus <jdetiber@redhat.com>
Date: Sat, 18 Feb 2017 20:01:53 -0500
Subject: lib_openshift oc file lookup improvements

- Move binary file search to separate method called from constructor
- Use six.PY3 instaed of sys.version_info
- Combine additional paths with the paths from the environment
  - For py3 pass the combined paths to shutil.which
  - For py2 explictly search for existance of file from combined paths
    instead of using distutils.spawn.find_executable and falling back
- Use 'oc adm' instead of 'oadm'

- Fix generate_validation test issue
- fix tests for oc binary location
- add tests for file lookup
---
 roles/lib_openshift/library/oadm_manage_node.py    |  45 ++++---
 roles/lib_openshift/library/oc_adm_registry.py     |  39 +++++-
 roles/lib_openshift/library/oc_adm_router.py       |  39 +++++-
 roles/lib_openshift/library/oc_edit.py             |  45 ++++---
 roles/lib_openshift/library/oc_env.py              |  45 ++++---
 roles/lib_openshift/library/oc_label.py            |  45 ++++---
 roles/lib_openshift/library/oc_obj.py              |  45 ++++---
 roles/lib_openshift/library/oc_process.py          |  45 ++++---
 roles/lib_openshift/library/oc_route.py            |  45 ++++---
 roles/lib_openshift/library/oc_scale.py            |  45 ++++---
 roles/lib_openshift/library/oc_secret.py           |  45 ++++---
 roles/lib_openshift/library/oc_service.py          |  45 ++++---
 roles/lib_openshift/library/oc_serviceaccount.py   |  45 ++++---
 .../library/oc_serviceaccount_secret.py            |  45 ++++---
 roles/lib_openshift/library/oc_version.py          |  45 ++++---
 roles/lib_openshift/src/lib/base.py                |  44 +++++--
 roles/lib_openshift/src/lib/import.py              |   1 -
 .../src/test/unit/test_oadm_manage_node.py         | 111 ++++++++++++++++-
 roles/lib_openshift/src/test/unit/test_oc_env.py   | 132 ++++++++++++++++++++-
 roles/lib_openshift/src/test/unit/test_oc_label.py | 111 ++++++++++++++++-
 .../lib_openshift/src/test/unit/test_oc_process.py | 111 ++++++++++++++++-
 roles/lib_openshift/src/test/unit/test_oc_route.py | 125 ++++++++++++++++++-
 roles/lib_openshift/src/test/unit/test_oc_scale.py | 111 ++++++++++++++++-
 .../lib_openshift/src/test/unit/test_oc_secret.py  | 118 +++++++++++++++++-
 .../lib_openshift/src/test/unit/test_oc_service.py | 111 ++++++++++++++++-
 .../src/test/unit/test_oc_serviceaccount.py        | 118 +++++++++++++++++-
 .../src/test/unit/test_oc_serviceaccount_secret.py | 125 ++++++++++++++++++-
 .../lib_openshift/src/test/unit/test_oc_version.py | 111 ++++++++++++++++-
 28 files changed, 1766 insertions(+), 226 deletions(-)

(limited to 'roles/lib_openshift')

diff --git a/roles/lib_openshift/library/oadm_manage_node.py b/roles/lib_openshift/library/oadm_manage_node.py
index 840882bbc..f2035c973 100644
--- a/roles/lib_openshift/library/oadm_manage_node.py
+++ b/roles/lib_openshift/library/oadm_manage_node.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -749,6 +748,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -762,6 +787,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -963,19 +989,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_adm_registry.py b/roles/lib_openshift/library/oc_adm_registry.py
index 32a4f8637..5be9e3e0a 100644
--- a/roles/lib_openshift/library/oc_adm_registry.py
+++ b/roles/lib_openshift/library/oc_adm_registry.py
@@ -852,6 +852,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -865,6 +891,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -1066,11 +1093,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
+        cmds = [self.oc_binary]
+
         if oadm:
-            cmds = ['oadm']
-        else:
-            cmds = ['oc']
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -1086,7 +1112,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_adm_router.py b/roles/lib_openshift/library/oc_adm_router.py
index e6d0f795e..673c389a5 100644
--- a/roles/lib_openshift/library/oc_adm_router.py
+++ b/roles/lib_openshift/library/oc_adm_router.py
@@ -877,6 +877,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -890,6 +916,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -1091,11 +1118,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
+        cmds = [self.oc_binary]
+
         if oadm:
-            cmds = ['oadm']
-        else:
-            cmds = ['oc']
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
@@ -1111,7 +1137,10 @@ class OpenShiftCLI(object):
         if self.verbose:
             print(' '.join(cmds))
 
-        returncode, stdout, stderr = self._run(cmds, input_data)
+        try:
+            returncode, stdout, stderr = self._run(cmds, input_data)
+        except OSError as ex:
+            returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
 
         rval = {"returncode": returncode,
                 "results": results,
diff --git a/roles/lib_openshift/library/oc_edit.py b/roles/lib_openshift/library/oc_edit.py
index 9d174ba31..e38cf443f 100644
--- a/roles/lib_openshift/library/oc_edit.py
+++ b/roles/lib_openshift/library/oc_edit.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -777,6 +776,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -790,6 +815,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -991,19 +1017,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_env.py b/roles/lib_openshift/library/oc_env.py
index d32773c38..1e089198f 100644
--- a/roles/lib_openshift/library/oc_env.py
+++ b/roles/lib_openshift/library/oc_env.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -744,6 +743,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -757,6 +782,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -958,19 +984,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_label.py b/roles/lib_openshift/library/oc_label.py
index 0f98728f8..981e4ef2d 100644
--- a/roles/lib_openshift/library/oc_label.py
+++ b/roles/lib_openshift/library/oc_label.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -753,6 +752,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -766,6 +791,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -967,19 +993,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_obj.py b/roles/lib_openshift/library/oc_obj.py
index 39da0ed5c..3b9c79fae 100644
--- a/roles/lib_openshift/library/oc_obj.py
+++ b/roles/lib_openshift/library/oc_obj.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -756,6 +755,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -769,6 +794,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -970,19 +996,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_process.py b/roles/lib_openshift/library/oc_process.py
index d3b2de4dc..996dcefa1 100644
--- a/roles/lib_openshift/library/oc_process.py
+++ b/roles/lib_openshift/library/oc_process.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -745,6 +744,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -758,6 +783,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -959,19 +985,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_route.py b/roles/lib_openshift/library/oc_route.py
index d8d5e1891..cc096bb88 100644
--- a/roles/lib_openshift/library/oc_route.py
+++ b/roles/lib_openshift/library/oc_route.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -787,6 +786,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -800,6 +825,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -1001,19 +1027,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_scale.py b/roles/lib_openshift/library/oc_scale.py
index e2a004f54..b70be4dad 100644
--- a/roles/lib_openshift/library/oc_scale.py
+++ b/roles/lib_openshift/library/oc_scale.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -731,6 +730,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -744,6 +769,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -945,19 +971,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_secret.py b/roles/lib_openshift/library/oc_secret.py
index 23d61a4fb..68213d4d9 100644
--- a/roles/lib_openshift/library/oc_secret.py
+++ b/roles/lib_openshift/library/oc_secret.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -777,6 +776,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -790,6 +815,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -991,19 +1017,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_service.py b/roles/lib_openshift/library/oc_service.py
index 6e7b1b52e..35c00c189 100644
--- a/roles/lib_openshift/library/oc_service.py
+++ b/roles/lib_openshift/library/oc_service.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -783,6 +782,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -796,6 +821,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -997,19 +1023,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_serviceaccount.py b/roles/lib_openshift/library/oc_serviceaccount.py
index 59dee8efb..2feb7f32e 100644
--- a/roles/lib_openshift/library/oc_serviceaccount.py
+++ b/roles/lib_openshift/library/oc_serviceaccount.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -729,6 +728,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -742,6 +767,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -943,19 +969,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_serviceaccount_secret.py b/roles/lib_openshift/library/oc_serviceaccount_secret.py
index 906134797..676337149 100644
--- a/roles/lib_openshift/library/oc_serviceaccount_secret.py
+++ b/roles/lib_openshift/library/oc_serviceaccount_secret.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -729,6 +728,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -742,6 +767,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -943,19 +969,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/library/oc_version.py b/roles/lib_openshift/library/oc_version.py
index eeb28e7fc..933bcc331 100644
--- a/roles/lib_openshift/library/oc_version.py
+++ b/roles/lib_openshift/library/oc_version.py
@@ -36,7 +36,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
@@ -701,6 +700,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -714,6 +739,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -915,19 +941,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/src/lib/base.py b/roles/lib_openshift/src/lib/base.py
index 0018a6710..79ea3a6b9 100644
--- a/roles/lib_openshift/src/lib/base.py
+++ b/roles/lib_openshift/src/lib/base.py
@@ -9,6 +9,32 @@ class OpenShiftCLIError(Exception):
     pass
 
 
+ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
+
+
+def locate_oc_binary():
+    ''' Find and return oc binary file '''
+    # https://github.com/openshift/openshift-ansible/issues/3410
+    # oc can be in /usr/local/bin in some cases, but that may not
+    # be in $PATH due to ansible/sudo
+    paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
+
+    oc_binary = 'oc'
+
+    # Use shutil.which if it is available, otherwise fallback to a naive path search
+    try:
+        which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
+        if which_result is not None:
+            oc_binary = which_result
+    except AttributeError:
+        for path in paths:
+            if os.path.exists(os.path.join(path, oc_binary)):
+                oc_binary = os.path.join(path, oc_binary)
+                break
+
+    return oc_binary
+
+
 # pylint: disable=too-few-public-methods
 class OpenShiftCLI(object):
     ''' Class to wrap the command line tools '''
@@ -22,6 +48,7 @@ class OpenShiftCLI(object):
         self.verbose = verbose
         self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
         self.all_namespaces = all_namespaces
+        self.oc_binary = locate_oc_binary()
 
     # Pylint allows only 5 arguments to be passed.
     # pylint: disable=too-many-arguments
@@ -223,19 +250,10 @@ class OpenShiftCLI(object):
     # pylint: disable=too-many-arguments,too-many-branches
     def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
         '''Base command for oc '''
-        cmds = []
-        basecmd = 'oadm' if oadm else 'oc'
-        # https://github.com/openshift/openshift-ansible/issues/3410
-        # oc can be in /usr/local/bin in some cases, but that may not
-        # be in $PATH due to ansible/sudo
-        if sys.version_info[0] == 3:
-            basepath = shutil.which(basecmd) # pylint: disable=no-member
-        else:
-            import distutils.spawn
-            basepath = distutils.spawn.find_executable(basecmd) # pylint: disable=no-name-in-module
-        if basepath is None and os.path.isfile('/usr/local/bin/' + basecmd):
-            basecmd = '/usr/local/bin/' + basecmd
-        cmds.append(basecmd)
+        cmds = [self.oc_binary]
+
+        if oadm:
+            cmds.append('adm')
 
         if self.all_namespaces:
             cmds.extend(['--all-namespaces'])
diff --git a/roles/lib_openshift/src/lib/import.py b/roles/lib_openshift/src/lib/import.py
index 5b2479b87..a79297898 100644
--- a/roles/lib_openshift/src/lib/import.py
+++ b/roles/lib_openshift/src/lib/import.py
@@ -10,7 +10,6 @@ import atexit
 import copy
 import json
 import os
-import sys
 import re
 import shutil
 import subprocess
diff --git a/roles/lib_openshift/src/test/unit/test_oadm_manage_node.py b/roles/lib_openshift/src/test/unit/test_oadm_manage_node.py
index b0786dfac..761c849fb 100755
--- a/roles/lib_openshift/src/test/unit/test_oadm_manage_node.py
+++ b/roles/lib_openshift/src/test/unit/test_oadm_manage_node.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oadm_manage_node import ManageNode  # noqa: E402
+from oadm_manage_node import ManageNode, locate_oc_binary  # noqa: E402
 
 
 class ManageNodeTest(unittest.TestCase):
@@ -179,6 +180,114 @@ class ManageNodeTest(unittest.TestCase):
         self.assertEqual(results['results']['nodes'][0]['name'], 'ip-172-31-49-140.ec2.internal')
         self.assertEqual(results['results']['nodes'][0]['schedulable'], False)
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_env.py b/roles/lib_openshift/src/test/unit/test_oc_env.py
index 15bd7e464..dab5099c2 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_env.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_env.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_env import OCEnv  # noqa: E402
+from oc_env import OCEnv, locate_oc_binary  # noqa: E402
 
 
 class OCEnvTest(unittest.TestCase):
@@ -35,9 +36,10 @@ class OCEnvTest(unittest.TestCase):
         ''' setup method will create a file and set to known configuration '''
         pass
 
+    @mock.patch('oc_env.locate_oc_binary')
     @mock.patch('oc_env.Utils.create_tmpfile_copy')
     @mock.patch('oc_env.OCEnv._run')
-    def test_listing_all_env_vars(self, mock_cmd, mock_tmpfile_copy):
+    def test_listing_all_env_vars(self, mock_cmd, mock_tmpfile_copy, mock_oc_binary):
         ''' Testing listing all environment variables from a dc'''
 
         # Arrange
@@ -123,6 +125,10 @@ class OCEnvTest(unittest.TestCase):
             (0, dc_results, ''),  # First call to the mock
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mock_adminkubeconfig',
         ]
@@ -144,9 +150,10 @@ class OCEnvTest(unittest.TestCase):
             mock.call(['oc', '-n', 'default', 'get', 'dc', 'router', '-o', 'json'], None),
         ])
 
+    @mock.patch('oc_env.locate_oc_binary')
     @mock.patch('oc_env.Utils.create_tmpfile_copy')
     @mock.patch('oc_env.OCEnv._run')
-    def test_adding_env_vars(self, mock_cmd, mock_tmpfile_copy):
+    def test_adding_env_vars(self, mock_cmd, mock_tmpfile_copy, mock_oc_binary):
         ''' Test add environment variables to a dc'''
 
         # Arrange
@@ -304,6 +311,10 @@ class OCEnvTest(unittest.TestCase):
             (0, dc_results_after, ''),
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mock_adminkubeconfig',
         ]
@@ -325,9 +336,10 @@ class OCEnvTest(unittest.TestCase):
             mock.call(['oc', '-n', 'default', 'get', 'dc', 'router', '-o', 'json'], None),
         ])
 
+    @mock.patch('oc_env.locate_oc_binary')
     @mock.patch('oc_env.Utils.create_tmpfile_copy')
     @mock.patch('oc_env.OCEnv._run')
-    def test_removing_env_vars(self, mock_cmd, mock_tmpfile_copy):
+    def test_removing_env_vars(self, mock_cmd, mock_tmpfile_copy, mock_oc_binary):
         ''' Test add environment variables to a dc'''
 
         # Arrange
@@ -419,6 +431,10 @@ class OCEnvTest(unittest.TestCase):
             (0, '', ''),
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mock_adminkubeconfig',
         ]
@@ -435,6 +451,114 @@ class OCEnvTest(unittest.TestCase):
             mock.call(['oc', '-n', 'default', 'get', 'dc', 'router', '-o', 'json'], None),
         ])
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_label.py b/roles/lib_openshift/src/test/unit/test_oc_label.py
index 3176987b0..933b5f221 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_label.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_label.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_label import OCLabel  # noqa: E402
+from oc_label import OCLabel, locate_oc_binary  # noqa: E402
 
 
 class OCLabelTest(unittest.TestCase):
@@ -187,6 +188,114 @@ class OCLabelTest(unittest.TestCase):
         self.assertTrue(results['results']['results']['labels'][0] ==
                         {'storage_pv_quota': 'False', 'awesomens': 'testinglabel'})
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_process.py b/roles/lib_openshift/src/test/unit/test_oc_process.py
index 450ff7071..c4b36928b 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_process.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_process.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_process import OCProcess  # noqa: E402
+from oc_process import OCProcess, locate_oc_binary  # noqa: E402
 
 
 # pylint: disable=too-many-public-methods
@@ -474,6 +475,114 @@ class OCProcessTest(unittest.TestCase):
         self.assertFalse(results['changed'])
         self.assertEqual(results['results']['results']['items'][0]['metadata']['name'], 'testdb')
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_route.py b/roles/lib_openshift/src/test/unit/test_oc_route.py
index 361b61f4b..ea94bfabd 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_route.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_route.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_route import OCRoute  # noqa: E402
+from oc_route import OCRoute, locate_oc_binary  # noqa: E402
 
 
 class OCRouteTest(unittest.TestCase):
@@ -35,9 +36,10 @@ class OCRouteTest(unittest.TestCase):
         ''' setup method will create a file and set to known configuration '''
         pass
 
+    @mock.patch('oc_route.locate_oc_binary')
     @mock.patch('oc_route.Utils.create_tmpfile_copy')
     @mock.patch('oc_route.OCRoute._run')
-    def test_list_route(self, mock_cmd, mock_tmpfile_copy):
+    def test_list_route(self, mock_cmd, mock_tmpfile_copy, mock_oc_binary):
         ''' Testing getting a route '''
 
         # Arrange
@@ -115,6 +117,10 @@ class OCRouteTest(unittest.TestCase):
             (0, route_result, ''),
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mock.kubeconfig',
         ]
@@ -132,10 +138,11 @@ class OCRouteTest(unittest.TestCase):
             mock.call(['oc', '-n', 'default', 'get', 'route', 'test', '-o', 'json'], None),
         ])
 
+    @mock.patch('oc_route.locate_oc_binary')
     @mock.patch('oc_route.Utils.create_tmpfile_copy')
     @mock.patch('oc_route.Yedit._write')
     @mock.patch('oc_route.OCRoute._run')
-    def test_create_route(self, mock_cmd, mock_write, mock_tmpfile_copy):
+    def test_create_route(self, mock_cmd, mock_write, mock_tmpfile_copy, mock_oc_binary):
         ''' Testing getting a route '''
         # Arrange
 
@@ -235,6 +242,10 @@ metadata:
             (0, route_result, ''),
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mock.kubeconfig',
         ]
@@ -259,6 +270,114 @@ metadata:
             mock.call(['oc', '-n', 'default', 'get', 'route', 'test', '-o', 'json'], None),
         ])
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_scale.py b/roles/lib_openshift/src/test/unit/test_oc_scale.py
index f15eb164d..b2dec2fbe 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_scale.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_scale.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_scale import OCScale  # noqa: E402
+from oc_scale import OCScale, locate_oc_binary  # noqa: E402
 
 
 class OCScaleTest(unittest.TestCase):
@@ -158,6 +159,114 @@ class OCScaleTest(unittest.TestCase):
         self.assertTrue(results['failed'])
         self.assertEqual(results['msg']['returncode'], 1)
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_secret.py b/roles/lib_openshift/src/test/unit/test_oc_secret.py
index 645aac82b..087c62dcf 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_secret.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_secret.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_secret import OCSecret  # noqa: E402
+from oc_secret import OCSecret, locate_oc_binary  # noqa: E402
 
 
 class OCSecretTest(unittest.TestCase):
@@ -35,10 +36,11 @@ class OCSecretTest(unittest.TestCase):
         ''' setup method will create a file and set to known configuration '''
         pass
 
+    @mock.patch('oc_secret.locate_oc_binary')
     @mock.patch('oc_secret.Utils.create_tmpfile_copy')
     @mock.patch('oc_secret.Utils._write')
     @mock.patch('oc_secret.OCSecret._run')
-    def test_adding_a_secret(self, mock_cmd, mock_write, mock_tmpfile_copy):
+    def test_adding_a_secret(self, mock_cmd, mock_write, mock_tmpfile_copy, mock_oc_binary):
         ''' Testing adding a secret '''
 
         # Arrange
@@ -65,6 +67,10 @@ class OCSecretTest(unittest.TestCase):
             (0, 'secret/testsecretname', ''),
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mocked_kubeconfig',
         ]
@@ -87,6 +93,114 @@ class OCSecretTest(unittest.TestCase):
             mock.call(mock.ANY, "{'one': 1, 'two': 2, 'three': 3}"),
         ])
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_service.py b/roles/lib_openshift/src/test/unit/test_oc_service.py
index 4a845e9f3..8974eb6c6 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_service.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_service.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_service import OCService  # noqa: E402
+from oc_service import OCService, locate_oc_binary  # noqa: E402
 
 
 # pylint: disable=too-many-public-methods
@@ -207,6 +208,114 @@ class OCServiceTest(unittest.TestCase):
         self.assertTrue(results['results']['returncode'] == 0)
         self.assertEqual(results['results']['results'][0]['metadata']['name'], 'router')
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_serviceaccount.py b/roles/lib_openshift/src/test/unit/test_oc_serviceaccount.py
index 256b569eb..b02b37053 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_serviceaccount.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_serviceaccount.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_serviceaccount import OCServiceAccount  # noqa: E402
+from oc_serviceaccount import OCServiceAccount, locate_oc_binary  # noqa: E402
 
 
 class OCServiceAccountTest(unittest.TestCase):
@@ -35,9 +36,10 @@ class OCServiceAccountTest(unittest.TestCase):
         ''' setup method will create a file and set to known configuration '''
         pass
 
+    @mock.patch('oc_serviceaccount.locate_oc_binary')
     @mock.patch('oc_serviceaccount.Utils.create_tmpfile_copy')
     @mock.patch('oc_serviceaccount.OCServiceAccount._run')
-    def test_adding_a_serviceaccount(self, mock_cmd, mock_tmpfile_copy):
+    def test_adding_a_serviceaccount(self, mock_cmd, mock_tmpfile_copy, mock_oc_binary):
         ''' Testing adding a serviceaccount '''
 
         # Arrange
@@ -91,6 +93,10 @@ class OCServiceAccountTest(unittest.TestCase):
             (0, valid_result_json, ''),
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mocked_kubeconfig',
         ]
@@ -110,6 +116,114 @@ class OCServiceAccountTest(unittest.TestCase):
             mock.call(['oc', '-n', 'default', 'get', 'sa', 'testserviceaccountname', '-o', 'json'], None),
         ])
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_serviceaccount_secret.py b/roles/lib_openshift/src/test/unit/test_oc_serviceaccount_secret.py
index 213c581aa..ab8ccd18c 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_serviceaccount_secret.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_serviceaccount_secret.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_serviceaccount_secret import OCServiceAccountSecret  # noqa: E402
+from oc_serviceaccount_secret import OCServiceAccountSecret, locate_oc_binary  # noqa: E402
 
 try:
     import ruamel.yaml as yaml  # noqa: EF401
@@ -41,10 +42,11 @@ class OCServiceAccountSecretTest(unittest.TestCase):
         ''' setup method will create a file and set to known configuration '''
         pass
 
+    @mock.patch('oc_serviceaccount_secret.locate_oc_binary')
     @mock.patch('oc_serviceaccount_secret.Utils.create_tmpfile_copy')
     @mock.patch('oc_serviceaccount_secret.Yedit._write')
     @mock.patch('oc_serviceaccount_secret.OCServiceAccountSecret._run')
-    def test_adding_a_secret_to_a_serviceaccount(self, mock_cmd, mock_write, mock_tmpfile_copy):
+    def test_adding_a_secret_to_a_serviceaccount(self, mock_cmd, mock_write, mock_tmpfile_copy, mock_oc_binary):
         ''' Testing adding a secret to a service account '''
 
         # Arrange
@@ -161,6 +163,10 @@ secrets:
             (0, oc_get_sa_after, ''),  # Fourth call to the mock
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mocked_kubeconfig',
         ]
@@ -189,10 +195,11 @@ secrets:
             mock.call(mock.ANY, yaml_file)
         ])
 
+    @mock.patch('oc_serviceaccount_secret.locate_oc_binary')
     @mock.patch('oc_serviceaccount_secret.Utils.create_tmpfile_copy')
     @mock.patch('oc_serviceaccount_secret.Yedit._write')
     @mock.patch('oc_serviceaccount_secret.OCServiceAccountSecret._run')
-    def test_removing_a_secret_to_a_serviceaccount(self, mock_cmd, mock_write, mock_tmpfile_copy):
+    def test_removing_a_secret_to_a_serviceaccount(self, mock_cmd, mock_write, mock_tmpfile_copy, mock_oc_binary):
         ''' Testing removing a secret to a service account '''
 
         # Arrange
@@ -279,6 +286,10 @@ secrets:
             (0, 'serviceaccount "builder" replaced', ''),  # Third call to the mock
         ]
 
+        mock_oc_binary.side_effect = [
+            'oc'
+        ]
+
         mock_tmpfile_copy.side_effect = [
             '/tmp/mocked_kubeconfig',
         ]
@@ -306,6 +317,114 @@ secrets:
             mock.call(mock.ANY, yaml_file)
         ])
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
diff --git a/roles/lib_openshift/src/test/unit/test_oc_version.py b/roles/lib_openshift/src/test/unit/test_oc_version.py
index 67dea415b..6daf5b00d 100755
--- a/roles/lib_openshift/src/test/unit/test_oc_version.py
+++ b/roles/lib_openshift/src/test/unit/test_oc_version.py
@@ -11,6 +11,7 @@
 # OK
 
 import os
+import six
 import sys
 import unittest
 import mock
@@ -23,7 +24,7 @@ import mock
 # place class in our python path
 module_path = os.path.join('/'.join(os.path.realpath(__file__).split('/')[:-4]), 'library')  # noqa: E501
 sys.path.insert(0, module_path)
-from oc_version import OCVersion  # noqa: E402
+from oc_version import OCVersion, locate_oc_binary  # noqa: E402
 
 
 class OCVersionTest(unittest.TestCase):
@@ -64,6 +65,114 @@ class OCVersionTest(unittest.TestCase):
         self.assertEqual(results['results']['oc_numeric'], '3.4.0.39')
         self.assertEqual(results['results']['kubernetes_numeric'], '1.4.0')
 
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_path_exists.side_effect = lambda _: False
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY3, 'py2 test only')
+    @mock.patch('os.path.exists')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home(self, mock_env_get, mock_path_exists):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_path_exists.side_effect = lambda f: f == oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_fallback_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup fallback '''
+
+        mock_env_get.side_effect = lambda _v, _d: ''
+
+        mock_shutil_which.side_effect = lambda _f, path=None: None
+
+        self.assertEqual(locate_oc_binary(), 'oc')
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_path_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in path '''
+
+        oc_bin = '/usr/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_usr_local_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in /usr/local/bin '''
+
+        oc_bin = '/usr/local/bin/oc'
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
+    @unittest.skipIf(six.PY2, 'py3 test only')
+    @mock.patch('shutil.which')
+    @mock.patch('os.environ.get')
+    def test_binary_lookup_in_home_py3(self, mock_env_get, mock_shutil_which):
+        ''' Testing binary lookup in ~/bin '''
+
+        oc_bin = os.path.expanduser('~/bin/oc')
+
+        mock_env_get.side_effect = lambda _v, _d: '/bin:/usr/bin'
+
+        mock_shutil_which.side_effect = lambda _f, path=None: oc_bin
+
+        self.assertEqual(locate_oc_binary(), oc_bin)
+
     def tearDown(self):
         '''TearDown method'''
         pass
-- 
cgit v1.2.3