def condition(self): """Gets the object describing the conditions for this build step""" node = self._root.find("condition") assert node is not None plugin = find_plugin(node.attrib["class"]) if not plugin: msg = "Conditional build step condition {0} not supported by PyJen." raise NotImplementedError(msg.format(node.attrib["class"])) return plugin(node)
def test_unsupported_plugin(caplog): with patch("pyjen.utils.plugin_api.iter_entry_points") as entry_points: mock_plugin_class = MagicMock(spec=[]) mock_ep = MagicMock() mock_ep.load.return_value = mock_plugin_class entry_points.return_value = [mock_ep] res = find_plugin("some_plugin") assert res is None assert "does not expose the required get_jenkins_plugin_name static method" in caplog.text
def condition(self): """Gets the object describing the conditions for this build step""" node = self._root.find("condition") assert node is not None plugin = find_plugin(node.attrib["class"]) if not plugin: raise PluginNotSupportedError( "Conditional build step condition %s not supported by PyJen.", node.attrib["class"] ) return plugin(node)
def instantiate(json_data, rest_api): """Factory method for finding the appropriate PyJen view object based on data loaded from the Jenkins REST API :param dict json_data: data loaded from the Jenkins REST API summarizing the view to be instantiated :param rest_api: PyJen REST API configured for use by the parent container. Will be used to instantiate the PyJen view that is returned. :returns: PyJen view object wrapping the REST API for the given Jenkins view :rtype: :class:`~.view.View` """ # TODO: Find some way to cache the json data given inside the view so # we can lazy-load API data. For example, the name of the view # is always included in the json data and therefore queries for # the View name after creation should not require another hit # to the REST API log = logging.getLogger(__name__) # The default view will not have a valid view URL # so we need to look for this and generate a corrected one view_url = json_data["url"] if '/view/' not in view_url: view_url = view_url + "view/" + json_data["name"] # Extract the name of the Jenkins plugin associated with this view # Sanity Check: make sure the metadata for the view has a "_class" # attribute. I'm pretty sure older version of the Jenkins # core did not expose such an attribute, but all versions # from the past 2+ years or more do appear to include it. # If, for some reason this attribute doesn't exist, we'll # fall back to the default view base class which provides # functionality common to all Jenkins views. For extra # debugging purposes however, we log some debug output # if we ever hit this case so we can investigate the # the details further. plugin_class = None if "_class" in json_data: plugin_class = find_plugin(json_data["_class"]) else: # pragma: no cover log.debug("Unsupported Jenkins version detected. Views are " "expected to have a _class metadata attribute but this " "one does not: %s", json_data) if not plugin_class: log.debug("Unable to find plugin for class %s", json_data["_class"]) plugin_class = View return plugin_class(rest_api.clone(view_url))
def job(self): """Gets the Jenkins job associated with this scheduled build May return None if this queue item has been invalidated by Jenkins :rtype: :class:`pyjen.job.Job` """ job_data = self._data.get("task") if job_data is None: return None plugin = find_plugin(job_data["_class"]) if plugin is None: raise PluginNotSupportedError( "Job plugin not supported.", job_data["_class"]) return plugin(self._api.clone(job_data["url"]))
def triggers(self): """list of trigger operations defined for this instance of the plugin :rtype: :class:`list` of :class:`BuildTriggerConfig` objects """ retval = list() configs_node = self._root.find('configs') for config in configs_node: plugin = find_plugin(config.tag) if plugin is None: self._log.warning("Skipping unsupported plugin: %s", config.tag) continue retval.append(plugin(config)) return retval
def test_one_supported_plugin(caplog): with patch("pyjen.utils.plugin_api.iter_entry_points") as entry_points: expected_plugin_name = "some_plugin" mock_plugin_class = MagicMock() mock_plugin_class.get_jenkins_plugin_name.return_value = expected_plugin_name mock_ep = MagicMock() mock_ep.load.return_value = mock_plugin_class entry_points.return_value = [mock_ep] res = find_plugin(expected_plugin_name) assert res is not None assert res == mock_plugin_class assert not caplog.text
def scm(self): """Gets the source code repo where the build script is located""" definition_node = self._root.find("definition") if "CpsScmFlowDefinition" not in definition_node.attrib["class"]: return None scm_node = definition_node.find("scm") assert scm_node is not None assert "class" in scm_node.attrib plugin_name = scm_node.attrib["class"] plugin = find_plugin(plugin_name) if plugin is None: raise NotImplementedError( "PyJen has no plugin installed for Jenkins plugin: " + plugin_name) return plugin(scm_node)
def job(self): """Gets the Jenkins job associated with this scheduled build May return None if this queue item has been invalidated by Jenkins :rtype: :class:`pyjen.job.Job` """ job_data = self._data.get("task") if job_data is None: return None plugin = find_plugin(job_data["_class"]) if plugin is None: raise NotImplementedError( "Job plugin not supported: " + job_data["_class"]) return plugin(self._api.clone(job_data["url"]))
def add_section(self, section_type, name): """Adds a new section to the sectioned view :param str section_type: name of class used to implement the new section to add :param str name: descriptive text to appear in the title area of the section """ plugin_class = find_plugin(section_type) if not plugin_class: raise PluginNotSupportedError( "Failed loading Sectioned View section", section_type) new_section = plugin_class.create(name) new_section.parent = self sections = self._root.find('sections') sections.append(new_section.node)
def instantiate(json_data, rest_api): """Factory method for finding the appropriate PyJen view object based on data loaded from the Jenkins REST API :param dict json_data: data loaded from the Jenkins REST API summarizing the view to be instantiated :param rest_api: PyJen REST API configured for use by the parent container. Will be used to instantiate the PyJen view that is returned. :returns: PyJen view object wrapping the REST API for the given Jenkins view :rtype: :class:`~.view.View` """ # TODO: Find some way to cache the json data given inside the view so # we can lazy-load API data. For example, the name of the view # is always included in the json data and therefore queries for # the View name after creation should not require another hit # to the REST API log = logging.getLogger(__name__) job_url = json_data["url"] # Extract the name of the Jenkins plugin associated with this view # Sanity Check: make sure the metadata for the view has a "_class" # attribute. I'm pretty sure older version of the Jenkins # core did not expose such an attribute, but all versions # from the past 2+ years or more do appear to include it. # If, for some reason this attribute doesn't exist, we'll # fall back to the default view base class which provides # functionality common to all Jenkins views. For extra # debugging purposes however, we log some debug output # if we ever hit this case so we can investigate the # the details further. plugin_class = None if "_class" in json_data: plugin_class = find_plugin(json_data["_class"]) else: # pragma: no cover log.debug("Unsupported Jenkins version detected. Jobs are " "expected to have a _class metadata attribute but this " "one does not: %s", json_data) if not plugin_class: log.debug("Unable to find plugin for class %s", json_data["_class"]) plugin_class = Job return plugin_class(rest_api.clone(job_url))
def add_section(self, section_type, name): """Adds a new section to the sectioned view :param str section_type: name of class used to implement the new section to add :param str name: descriptive text to appear in the title area of the section """ plugin_class = find_plugin(section_type) if not plugin_class: raise NotImplementedError( "Failed loading Sectioned View section: " + section_type) new_section = plugin_class.instantiate(name) new_section.parent = self sections = self._root.find('sections') sections.append(new_section.node)
def builder(self): """Gets the build step managed by this condition """ build_step_node = self._root.find("buildStep") plugin = find_plugin(build_step_node.attrib["class"]) if not plugin: raise NotImplementedError( "Build step plugin {0} is not supported by PyJen.".format( build_step_node.attrib["class"])) # We have to reconstruct the XML for the build step from the # encoded version in the buildStep. For further details see # the encoding logic found in the create() method of this class. root_node = ElementTree.Element(build_step_node.attrib["class"]) for cur_child in build_step_node: root_node.append(cur_child) return plugin(root_node)
def properties(self): """Gets a list of 0 or more Jenkins properties associated with this job :returns: a list of customizable properties associated with this job :rtype: :class:`list` of property plugins supported by this job """ retval = list() nodes = self._root.find('properties') for node in nodes: plugin = find_plugin(node.tag) if plugin is not None: temp = plugin(node) temp.parent = self retval.append(temp) else: self._log.warning( "Unsupported job 'property' plugin: %s", node.tag) return retval
def parameters(self): """Gets a list of the build parameters associated with this property :rtype: :class:`list` of build parameters """ params_node = self._root.find("parameterDefinitions") assert params_node is not None retval = list() for cur_param in params_node: plugin = find_plugin(cur_param.tag) if plugin is None: self._log.warning("Skipping unsupported plugin: %s", cur_param.tag) continue retval.append(plugin(cur_param)) return retval
def sections(self): """ :returns: a list of all 'section' objects contained in this view :rtype: :class:`list` of section plugins associated with this view """ nodes = self._root.find('sections') retval = list() for node in nodes: plugin_class = find_plugin(node.tag) if plugin_class is None: self._log.warning("Sectioned view plugin not found: %s", node.tag) continue temp = plugin_class(node) temp.parent = self retval.append(temp) return retval
def builders(self): """Gets a list of 0 or more build operations associated with this job :returns: a list of build operations associated with this job :rtype: :class:`list` of builder plugins used by this job """ retval = list() nodes = self._root.find('builders') for node in nodes: plugin_class = find_plugin(node.tag) if plugin_class is None: self._log.warning("Unsupported job 'builder' plugin %s", node.tag) continue temp = plugin_class(node) temp.parent = self retval.append(temp) return retval
def publishers(self): """list of 0 or more post-build publishers associated with this job :returns: a list of post-build publishers associated with this job :rtype: :class:`list` of publisher plugins supported by this job """ retval = list() nodes = self._root.find('publishers') for node in nodes: plugin_class = find_plugin(node.tag) if plugin_class is None: self._log.warning("Unsupported job 'publisher' plugin: %s", node.tag) continue retval.append(plugin_class(node)) return retval
def builder(self): """Gets the build step managed by this condition """ build_step_node = self._root.find("buildStep") plugin = find_plugin(build_step_node.attrib["class"]) if not plugin: raise NotImplementedError( "Build step plugin {0} is not supported by PyJen.".format( build_step_node.attrib["class"] ) ) # We have to reconstruct the XML for the build step from the # encoded version in the buildStep. For further details see # the encoding logic found in the create() method of this class. root_node = ElementTree.Element(build_step_node.attrib["class"]) for cur_child in build_step_node: root_node.append(cur_child) return plugin(root_node)
def test_multiple_supported_plugin(caplog): with patch("pyjen.utils.plugin_api.iter_entry_points") as entry_points: expected_plugin_name = "some_plugin" mock_plugin_class1 = MagicMock() mock_plugin_class1.get_jenkins_plugin_name.return_value = expected_plugin_name mock_plugin_class2 = MagicMock() mock_plugin_class2.get_jenkins_plugin_name.return_value = expected_plugin_name mock_ep1 = MagicMock() mock_ep1.load.return_value = mock_plugin_class1 mock_ep2 = MagicMock() mock_ep2.load.return_value = mock_plugin_class2 entry_points.return_value = [mock_ep1, mock_ep2] res = find_plugin(expected_plugin_name) assert res is not None assert res == mock_plugin_class1 assert "multiple plugins detected" in caplog.text
def scm(self): """Retrieves the appropriate plugin for the SCM portion of a job Detects which source code management tool is being used by this job, locates the appropriate plugin for that tool, and returns an instance of the wrapper for that plugin pre-configured with the settings found in the relevant XML subtree. :returns: One of any number of plugin objects responsible for providing extensions to the source code management portion of a job Examples: :class:`~pyjen.plugins.subversion.Subversion` :rtype: :class:`~.pluginapi.PluginBase` """ node = self._root.find('scm') plugin_class = find_plugin(node.attrib["class"]) if plugin_class is None: raise PluginNotSupportedError("SCM XML plugin not found", node.attrib["class"]) return plugin_class(node)