Repository: TheFriendlyCoder/pyjen

Issue:
Add getter to retrieve all jobs on a Jenkins build farm
It was recently brought to my attention that there is no easy way to get a list of all jobs managed by a Jenkins build farm. This should be fixed.

Discussion:

Patch:
diff --git a/src/pyjen/jenkins.py b/src/pyjen/jenkins.py
index 0f46fa8..cbbf69d 100644
--- a/src/pyjen/jenkins.py
+++ b/src/pyjen/jenkins.py
@@ -164,6 +164,20 @@ class Jenkins(object):
 
         return retval
 
+    @property
+    def jobs(self):
+        """Gets all jobs managed by this Jenkins instance
+
+        :rtype:  :class:`list` of :class:`~.job.Job` objects
+        """
+        data = self._api.get_api_data(query_params="depth=2")
+
+        retval = list()
+        for j in data['jobs']:
+            retval.append(Job.instantiate(j, self._api))
+
+        return retval
+
     def prepare_shutdown(self):
         """Starts a "quiet down" and prevents new builds from executing
 
diff --git a/src/pyjen/plugins/multibranch_pipeline.py b/src/pyjen/plugins/multibranch_pipeline.py
new file mode 100644
index 0000000..28f565f
--- /dev/null
+++ b/src/pyjen/plugins/multibranch_pipeline.py
@@ -0,0 +1,78 @@
+"""Primitives that manage Jenkins job of type 'multibranch pipeline'"""
+from pyjen.job import Job
+
+
+class MultibranchPipelineJob(Job):
+    """Jenkins job of type 'multibranch pipeline'"""
+
+    @property
+    def jobs(self):
+        """Gets all branch jobs managed by this multibranch pipeline
+
+        :rtype: :class:`list` of :class:`pyjen.plugins.pipelinejob.PipelineJob`
+        """
+        data = self._api.get_api_data(query_params="depth=2")
+
+        retval = list()
+        for j in data["jobs"]:
+            retval.append(Job.instantiate(j, self._api))
+
+        return retval
+
+    # --------------------------------------------------------------- PLUGIN API
+    @staticmethod
+    def get_jenkins_plugin_name():
+        """Gets the name of the Jenkins plugin associated with this PyJen plugin
+
+        This static method is used by the PyJen plugin API to associate this
+        class with a specific Jenkins plugin, as it is encoded in the config.xml
+
+        :rtype: :class:`str`
+        """
+        return "org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject"
+
+    @staticmethod
+    def template_config_xml():
+        return """<org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject plugin="workflow-multibranch@2.21">
+    <actions/>
+    <description/>
+    <properties>
+        <org.jenkinsci.plugins.pipeline.modeldefinition.config.FolderConfig plugin="pipeline-model-definition@1.3.8">
+            <dockerLabel/>
+            <registry plugin="docker-commons@1.14"/>
+        </org.jenkinsci.plugins.pipeline.modeldefinition.config.FolderConfig>
+    </properties>
+    <folderViews class="jenkins.branch.MultiBranchProjectViewHolder" plugin="branch-api@2.4.0">
+        <owner class="org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject" reference="../.."/>
+    </folderViews>
+    <healthMetrics>
+        <com.cloudbees.hudson.plugins.folder.health.WorstChildHealthMetric plugin="cloudbees-folder@6.8">
+            <nonRecursive>false</nonRecursive>
+        </com.cloudbees.hudson.plugins.folder.health.WorstChildHealthMetric>
+    </healthMetrics>
+    <icon class="jenkins.branch.MetadataActionFolderIcon" plugin="branch-api@2.4.0">
+        <owner class="org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject" reference="../.."/>
+    </icon>
+    <orphanedItemStrategy class="com.cloudbees.hudson.plugins.folder.computed.DefaultOrphanedItemStrategy" plugin="cloudbees-folder@6.8">
+        <pruneDeadBranches>true</pruneDeadBranches>
+        <daysToKeep>-1</daysToKeep>
+        <numToKeep>-1</numToKeep>
+    </orphanedItemStrategy>
+    <triggers/>
+    <disabled>false</disabled>
+    <sources class="jenkins.branch.MultiBranchProject$BranchSourceList" plugin="branch-api@2.4.0">
+        <data/>
+        <owner class="org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject" reference="../.."/>
+    </sources>
+    <factory class="org.jenkinsci.plugins.workflow.multibranch.WorkflowBranchProjectFactory">
+        <owner class="org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject" reference="../.."/>
+        <scriptPath>Jenkinsfile</scriptPath>
+    </factory>
+</org.jenkinsci.plugins.workflow.multibranch.WorkflowMultiBranchProject>"""
+
+
+PluginClass = MultibranchPipelineJob
+
+if __name__ == "__main__":  # pragma: no cover
+    pass
+
diff --git a/src/pyjen/plugins/multijob.py b/src/pyjen/plugins/multijob.py
new file mode 100644
index 0000000..f9fa0c1
--- /dev/null
+++ b/src/pyjen/plugins/multijob.py
@@ -0,0 +1,51 @@
+"""Primitives that manage Jenkins job of type 'MultiJob'"""
+from pyjen.job import Job
+
+
+class MultiJob(Job):
+    """Custom job type provided by the jenkins-multijob-plugin plugin
+
+    https://plugins.jenkins.io/jenkins-multijob-plugin
+    """
+    @staticmethod
+    def get_jenkins_plugin_name():
+        """Gets the name of the Jenkins plugin associated with this PyJen plugin
+
+        This static method is used by the PyJen plugin API to associate this
+        class with a specific Jenkins plugin, as it is encoded in the config.xml
+
+        :rtype: :class:`str`
+        """
+        return "com.tikal.jenkins.plugins.multijob.MultiJobProject"
+
+    @staticmethod
+    def template_config_xml():
+        return """<com.tikal.jenkins.plugins.multijob.MultiJobProject plugin="jenkins-multijob-plugin@1.32">
+    <description/>
+    <keepDependencies>false</keepDependencies>
+    <properties>
+        <hudson.plugins.buildblocker.BuildBlockerProperty plugin="build-blocker-plugin@1.7.3">
+            <useBuildBlocker>false</useBuildBlocker>
+            <blockLevel>GLOBAL</blockLevel>
+            <scanQueueFor>DISABLED</scanQueueFor>
+            <blockingJobs/>
+        </hudson.plugins.buildblocker.BuildBlockerProperty>
+    </properties>
+    <scm class="hudson.scm.NullSCM"/>
+    <canRoam>true</canRoam>
+    <disabled>false</disabled>
+    <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
+    <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
+    <triggers/>
+    <concurrentBuild>false</concurrentBuild>
+    <builders/>
+    <publishers/>
+    <buildWrappers/>
+    <pollSubjobs>false</pollSubjobs>
+    </com.tikal.jenkins.plugins.multijob.MultiJobProject>"""
+
+
+PluginClass = MultiJob
+
+if __name__ == "__main__":  # pragma: no cover
+    pass


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index a5103bf..bffb7a9 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -43,6 +43,7 @@ JENKINS_PLUGINS = [
     "conditional-buildstep",
     "parameterized-trigger",
     "build-blocker-plugin",
+    "jenkins-multijob-plugin",
 ]
 
 
diff --git a/tests/test_jenkins.py b/tests/test_jenkins.py
index 81426e6..98c5539 100644
--- a/tests/test_jenkins.py
+++ b/tests/test_jenkins.py
@@ -173,6 +173,25 @@ def test_get_plugin_manager(jenkins_env):
     assert pm is not None
 
 
+def test_get_no_jobs(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    res = jk.jobs
+
+    assert res is not None
+    assert isinstance(res, list)
+    assert len(res) == 0
+
+
+def test_get_one_job(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    jb = jk.create_job("test_get_one_job", FreestyleJob)
+    with clean_job(jb):
+        res = jk.jobs
+
+        assert len(res) == 1
+        assert res[0] == jb
+
+
 def test_create_job(jenkins_env):
     jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
     jb = jk.create_job("test_create_job", FreestyleJob)
diff --git a/tests/test_jobs/test_multibranch_pipeline_job.py b/tests/test_jobs/test_multibranch_pipeline_job.py
new file mode 100644
index 0000000..e2a2419
--- /dev/null
+++ b/tests/test_jobs/test_multibranch_pipeline_job.py
@@ -0,0 +1,29 @@
+import pytest
+from ..utils import clean_job
+from pyjen.jenkins import Jenkins
+from pyjen.plugins.multibranch_pipeline import MultibranchPipelineJob
+
+
+def test_create_multibranch_pipeline_job(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    expected_name = "test_create_multibranch_pipeline_job"
+    jb = jk.create_job(expected_name, MultibranchPipelineJob)
+    with clean_job(jb):
+        assert jb is not None
+        assert jb.name == expected_name
+
+
+def test_get_branch_jobs(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    expected_name = "test_get_branch_jobs"
+    jb = jk.create_job(expected_name, MultibranchPipelineJob)
+    with clean_job(jb):
+        res = jb.jobs
+
+        assert res is not None
+        assert isinstance(res, list)
+        assert len(res) == 0
+
+
+if __name__ == "__main__":
+    pytest.main([__file__, "-v", "-s"])
diff --git a/tests/test_jobs/test_multijob_job.py b/tests/test_jobs/test_multijob_job.py
new file mode 100644
index 0000000..0a3c146
--- /dev/null
+++ b/tests/test_jobs/test_multijob_job.py
@@ -0,0 +1,17 @@
+import pytest
+from ..utils import clean_job
+from pyjen.jenkins import Jenkins
+from pyjen.plugins.multijob import MultiJob
+
+
+def test_create_multijob_job(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    expected_name = "test_create_multijob_job"
+    jb = jk.create_job(expected_name, MultiJob)
+    with clean_job(jb):
+        assert jb is not None
+        assert jb.name == expected_name
+
+
+if __name__ == "__main__":
+    pytest.main([__file__, "-v", "-s"])