Repository: TheFriendlyCoder/pyjen

Issue:
Add helper methods to get installed plugins
In an effort to make it easier for implementers to query for a list of installed PyJen plugins, we should add a couple of helper functions to the plugin API to return the list of installed plugins. I'm thinking one helper that returns all plugins, one that returns the subset of plugins that derive from the View base class, and another that returns the subset that derives from the Job base class, would be most helpful.

We could probably use these helpers within the API itself as well as shorthands for locating plugins for specific purposes.

Comments:

Patch:
diff --git a/src/pyjen/job.py b/src/pyjen/job.py
index ee9fba0..179e689 100644
--- a/src/pyjen/job.py
+++ b/src/pyjen/job.py
@@ -2,7 +2,7 @@
 import logging
 from pyjen.build import Build
 from pyjen.utils.jobxml import JobXML
-from pyjen.utils.plugin_api import find_plugin
+from pyjen.utils.plugin_api import find_plugin, get_all_plugins
 
 
 class Job(object):
@@ -82,6 +82,18 @@ class Job(object):
 
         return plugin_class(rest_api.clone(job_url))
 
+    @classmethod
+    def get_supported_plugins(cls):
+        """Returns a list of PyJen plugins that derive from this class
+
+        :rtype: :class:`list` of :class:`class`
+        """
+        retval = list()
+        for cur_plugin in get_all_plugins():
+            if issubclass(cur_plugin, cls):
+                retval.append(cur_plugin)
+        return retval
+
     @property
     def name(self):
         """Returns the name of the job managed by this object
diff --git a/src/pyjen/utils/plugin_api.py b/src/pyjen/utils/plugin_api.py
index 0c925e2..0c26416 100644
--- a/src/pyjen/utils/plugin_api.py
+++ b/src/pyjen/utils/plugin_api.py
@@ -24,19 +24,9 @@ def find_plugin(plugin_name):
     formatted_plugin_name = plugin_name.replace("__", "_")
 
     log = logging.getLogger(__name__)
-    all_plugins = list()
-    for entry_point in iter_entry_points(group=PLUGIN_ENTRYPOINT_NAME):
-        all_plugins.append(entry_point.load())
 
     supported_plugins = list()
-    for cur_plugin in all_plugins:
-        if not hasattr(cur_plugin, PLUGIN_METHOD_NAME):
-            log.debug(
-                "Plugin %s does not expose the required %s static method.",
-                cur_plugin.__module__,
-                PLUGIN_METHOD_NAME)
-            continue
-
+    for cur_plugin in get_all_plugins():
         if getattr(cur_plugin, PLUGIN_METHOD_NAME)() == formatted_plugin_name:
             supported_plugins.append(cur_plugin)
 
@@ -50,5 +40,32 @@ def find_plugin(plugin_name):
     return supported_plugins[0]
 
 
+def get_all_plugins():
+    """Returns a list of all PyJen plugins installed on the system
+
+    :returns: list of 0 or more installed plugins
+    :rtype: :class:`list` of :class:`class`
+    """
+    log = logging.getLogger(__name__)
+    # First load all libraries that are registered with the PyJen plugin API
+    all_plugins = list()
+    for entry_point in iter_entry_points(group=PLUGIN_ENTRYPOINT_NAME):
+        all_plugins.append(entry_point.load())
+
+    # Next, filter out those that don't support the current version of our API
+    retval = list()
+    for cur_plugin in all_plugins:
+        if not hasattr(cur_plugin, PLUGIN_METHOD_NAME):
+            log.debug(
+                "Plugin %s does not expose the required %s static method.",
+                cur_plugin.__module__,
+                PLUGIN_METHOD_NAME)
+            continue
+
+        retval.append(cur_plugin)
+
+    return retval
+
+
 if __name__ == "__main__":  # pragma: no cover
     pass
diff --git a/src/pyjen/view.py b/src/pyjen/view.py
index 76ef326..2b94b3a 100644
--- a/src/pyjen/view.py
+++ b/src/pyjen/view.py
@@ -1,7 +1,7 @@
 """Primitives for interacting with Jenkins views"""
 import logging
 from pyjen.job import Job
-from pyjen.utils.plugin_api import find_plugin
+from pyjen.utils.plugin_api import find_plugin, get_all_plugins
 
 
 class View(object):
@@ -68,6 +68,18 @@ class View(object):
 
         return plugin_class(rest_api.clone(view_url))
 
+    @classmethod
+    def get_supported_plugins(cls):
+        """Returns a list of PyJen plugins that derive from this class
+
+        :rtype: :class:`list` of :class:`class`
+        """
+        retval = list()
+        for cur_plugin in get_all_plugins():
+            if issubclass(cur_plugin, cls):
+                retval.append(cur_plugin)
+        return retval
+
     @property
     def name(self):
         """Gets the display name for this view


Test patch:
diff --git a/tests/test_plugin_api.py b/tests/test_plugin_api.py
index a4f1c1d..3c65678 100644
--- a/tests/test_plugin_api.py
+++ b/tests/test_plugin_api.py
@@ -1,6 +1,9 @@
 import pytest
 from mock import patch, MagicMock
-from pyjen.utils.plugin_api import find_plugin
+from .utils import count_plugins
+from pyjen.utils.plugin_api import find_plugin, get_all_plugins
+from pyjen.view import View
+from pyjen.job import Job
 
 
 def test_unsupported_plugin(caplog):
@@ -54,23 +57,45 @@ def test_multiple_supported_plugin(caplog):
         assert "multiple plugins detected" in caplog.text
 
 
-@pytest.mark.skip("TODO: add helper to get list of all installed plugins to new plugin api")
 def test_list_plugins():
-    res = ConfigXML.get_installed_plugins()
+    res = get_all_plugins()
     assert res is not None
     assert isinstance(res, list)
     assert len(res) == count_plugins()
 
 
-@pytest.mark.skip("TODO: add helper to get list of all installed plugins to new plugin api")
-def test_load_all_plugins():
-    plugin_names = ConfigXML.get_installed_plugins()
-    assert plugin_names
-
-    for cur_plugin_name in plugin_names:
-        cur_plugin = ConfigXML.find_plugin(cur_plugin_name)
-        assert cur_plugin is not None
-        assert inspect.isclass(cur_plugin)
+def test_load_all_job_plugins():
+    plugins = Job.get_supported_plugins()
+    assert plugins is not None
+    assert isinstance(plugins, list)
+    assert len(plugins) > 0
+
+    mock_api = MagicMock()
+    expected_name = "FakeName"
+    mock_api.get_api_data.return_value = {
+        "name": expected_name
+    }
+    for cur_plugin in plugins:
+        job = cur_plugin(mock_api)
+        assert job.name == expected_name
+        assert isinstance(job, Job)
+
+
+def test_load_all_view_plugins():
+    plugins = View.get_supported_plugins()
+    assert plugins is not None
+    assert isinstance(plugins, list)
+    assert len(plugins) > 0
+
+    mock_api = MagicMock()
+    expected_name = "FakeName"
+    mock_api.get_api_data.return_value = {
+        "name": expected_name
+    }
+    for cur_plugin in plugins:
+        view = cur_plugin(mock_api)
+        assert view.name == expected_name
+        assert isinstance(view, View)
 
 
 if __name__ == "__main__":