From 67645a46ebaf29d0447049d8b40f39fef31bfb70 Mon Sep 17 00:00:00 2001 From: Vsevolod Fedorov Date: Tue, 12 Dec 2023 12:50:23 +0300 Subject: [PATCH] Fix legacy plugin version comparison; Remove cap on setuptools version LegacyVersion class is removed from newer setuptools package. But support for legacy versions is added in python-jenkins 1.8.2. Switch to that implementation. Fix broken plugin version comparison for legacy versions. Assume latest plugin version if no plugin version is found. Story: 2010990 Story: 2009943 Story: 2009819 Story: 2010842 Task: 49236 Task: 44852 Task: 44396 Task: 48448 Change-Id: Id7f0be1c42357454bd9bedcdee3fefb174943d81 --- jenkins_jobs/modules/builders.py | 13 +- jenkins_jobs/modules/helpers.py | 8 +- jenkins_jobs/modules/hipchat_notif.py | 10 +- jenkins_jobs/modules/project_maven.py | 8 +- jenkins_jobs/modules/properties.py | 18 ++- jenkins_jobs/modules/publishers.py | 61 ++++------ jenkins_jobs/modules/triggers.py | 42 ++----- jenkins_jobs/modules/wrappers.py | 44 +++---- jenkins_jobs/registry.py | 111 +++++++++--------- requirements.txt | 3 +- tests/conftest.py | 4 +- .../fixtures/hipchat002.plugins_info.yaml | 3 + tests/jsonparser/fixtures/complete001.xml | 1 + tests/moduleregistry/test_moduleregistry.py | 86 ++++++++------ tests/yamlparser/job_fixtures/complete001.xml | 1 + .../job_fixtures/include-raw-escape001.xml | 3 + .../job_fixtures/include-raw001.xml | 4 + .../job_fixtures/include-rawunicode001.xml | 1 + tests/yamlparser/job_fixtures/include001.xml | 4 + tests/yamlparser/job_fixtures/string_join.xml | 6 + tests/yamlparser/job_fixtures/unicode001.xml | 1 + 21 files changed, 203 insertions(+), 229 deletions(-) create mode 100644 tests/hipchat/fixtures/hipchat002.plugins_info.yaml diff --git a/jenkins_jobs/modules/builders.py b/jenkins_jobs/modules/builders.py index 739377146..8f01bed54 100644 --- a/jenkins_jobs/modules/builders.py +++ b/jenkins_jobs/modules/builders.py @@ -37,7 +37,6 @@ Example:: """ import logging -import sys import xml.etree.ElementTree as XML import six @@ -48,7 +47,6 @@ from jenkins_jobs.errors import JenkinsJobsException from jenkins_jobs.errors import MissingAttributeError import jenkins_jobs.modules.base import jenkins_jobs.modules.helpers as helpers -import pkg_resources from jenkins_jobs.modules import hudson_model from jenkins_jobs.modules.publishers import ssh from jenkins_jobs.modules.publishers import cifs @@ -2885,11 +2883,9 @@ def cmake(registry, xml_parent, data): ] helpers.convert_mapping_to_xml(cmake, data, mapping, fail_required=True) - info = registry.get_plugin_info("CMake plugin") - # Note: Assume latest version of plugin is preferred config format - version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("CMake plugin") - if version >= pkg_resources.parse_version("2.0"): + if plugin_ver >= "2.0": mapping_20 = [ ("preload-script", "preloadScript", None), # Optional parameter ("working-dir", "workingDir", ""), @@ -4701,8 +4697,7 @@ def xunit(registry, xml_parent, data): :language: yaml """ - info = registry.get_plugin_info("xunit") - plugin_version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("xunit") logger = logging.getLogger(__name__) xunit = XML.SubElement(xml_parent, "org.jenkinsci.plugins.xunit.XUnitBuilder") @@ -4744,7 +4739,7 @@ def xunit(registry, xml_parent, data): # Generate XML for each of the supported framework types # Note: versions 3+ are now using the 'tools' sub-element instead of 'types' - if plugin_version < pkg_resources.parse_version("3.0.0"): + if plugin_ver < "3.0.0": types_name = "types" else: types_name = "tools" diff --git a/jenkins_jobs/modules/helpers.py b/jenkins_jobs/modules/helpers.py index a0c1e7886..e8f568fed 100644 --- a/jenkins_jobs/modules/helpers.py +++ b/jenkins_jobs/modules/helpers.py @@ -14,7 +14,6 @@ from functools import wraps import logging -import sys import xml.etree.ElementTree as XML @@ -23,8 +22,6 @@ from jenkins_jobs.errors import JenkinsJobsException from jenkins_jobs.errors import MissingAttributeError from jenkins_jobs.modules import hudson_model -import pkg_resources - def build_trends_publisher(plugin_name, xml_element, data): """Helper to create various trend publishers.""" @@ -515,8 +512,7 @@ def trigger_get_parameter_order(registry, plugin): def trigger_project(tconfigs, project_def, registry, param_order=None): - info = registry.get_plugin_info("parameterized-trigger") - plugin_version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("parameterized-trigger") logger = logging.getLogger("%s:trigger_project" % __name__) pt_prefix = "hudson.plugins.parameterizedtrigger." @@ -554,7 +550,7 @@ def trigger_project(tconfigs, project_def, registry, param_order=None): ("fail-on-missing", "failTriggerOnMissing", False), ] - if plugin_version >= pkg_resources.parse_version("2.35.2"): + if plugin_ver >= "2.35.2": property_file_mapping.append( ("property-multiline", "textParamValueOnNewLine", False) ) diff --git a/jenkins_jobs/modules/hipchat_notif.py b/jenkins_jobs/modules/hipchat_notif.py index 0c9640f0d..0d00d50ff 100644 --- a/jenkins_jobs/modules/hipchat_notif.py +++ b/jenkins_jobs/modules/hipchat_notif.py @@ -74,7 +74,6 @@ Example: # and this object is passed to the HipChat() class initialiser. import logging -import pkg_resources import sys import xml.etree.ElementTree as XML @@ -137,10 +136,9 @@ class HipChat(jenkins_jobs.modules.base.Base): logger.warning("'room' is deprecated, please use 'rooms'") hipchat["rooms"] = [hipchat["room"]] - plugin_info = self.registry.get_plugin_info("Jenkins HipChat Plugin") - version = pkg_resources.parse_version(plugin_info.get("version", "0")) + plugin_ver = self.registry.get_plugin_version("Jenkins HipChat Plugin") - if version >= pkg_resources.parse_version("0.1.9"): + if plugin_ver >= "0.1.9": publishers = xml_parent.find("publishers") if publishers is None: publishers = XML.SubElement(xml_parent, "publishers") @@ -173,7 +171,7 @@ class HipChat(jenkins_jobs.modules.base.Base): hipchat.get("notify-start", hipchat.get("start-notify", False)) ).lower() - if version >= pkg_resources.parse_version("0.1.5"): + if plugin_ver >= "0.1.5": mapping = [ ("notify-success", "notifySuccess", False), ("notify-aborted", "notifyAborted", False), @@ -191,7 +189,7 @@ class HipChat(jenkins_jobs.modules.base.Base): publishers = XML.SubElement(xml_parent, "publishers") hippub = XML.SubElement(publishers, "jenkins.plugins.hipchat.HipChatNotifier") - if version >= pkg_resources.parse_version("0.1.8"): + if plugin_ver >= "0.1.8": XML.SubElement(hippub, "buildServerUrl").text = self.jenkinsUrl XML.SubElement(hippub, "sendAs").text = self.sendAs else: diff --git a/jenkins_jobs/modules/project_maven.py b/jenkins_jobs/modules/project_maven.py index 91178598c..10d72ed0a 100755 --- a/jenkins_jobs/modules/project_maven.py +++ b/jenkins_jobs/modules/project_maven.py @@ -82,7 +82,6 @@ CFP Example: .. literalinclude:: /../../tests/general/fixtures/project-maven003.yaml """ -import pkg_resources import xml.etree.ElementTree as XML from jenkins_jobs.errors import InvalidAttributeError @@ -106,8 +105,7 @@ class Maven(jenkins_jobs.modules.base.Base): return xml_parent # determine version of plugin - plugin_info = self.registry.get_plugin_info("Maven Integration plugin") - version = pkg_resources.parse_version(plugin_info.get("version", "0")) + plugin_ver = self.registry.get_plugin_version("Maven Integration plugin") if "root-module" in data["maven"]: root_module = XML.SubElement(xml_parent, "rootModule") @@ -160,9 +158,7 @@ class Maven(jenkins_jobs.modules.base.Base): XML.SubElement(xml_parent, "fingerprintingDisabled").text = str( not data["maven"].get("automatic-fingerprinting", True) ).lower() - if version > pkg_resources.parse_version( - "0" - ) and version < pkg_resources.parse_version("2.0.1"): + if plugin_ver < "2.0.1": XML.SubElement(xml_parent, "perModuleEmail").text = str( data.get("per-module-email", True) ).lower() diff --git a/jenkins_jobs/modules/properties.py b/jenkins_jobs/modules/properties.py index acf2d9748..b7cdb34c3 100644 --- a/jenkins_jobs/modules/properties.py +++ b/jenkins_jobs/modules/properties.py @@ -33,7 +33,6 @@ Example:: """ import logging -import pkg_resources import xml.etree.ElementTree as XML from jenkins_jobs.errors import InvalidAttributeError @@ -479,10 +478,9 @@ def inject(registry, xml_parent, data): helpers.convert_mapping_to_xml(info, data, mapping, fail_required=False) # determine version of plugin - plugin_info = registry.get_plugin_info("Groovy") - version = pkg_resources.parse_version(plugin_info.get("version", "0")) + plugin_ver = registry.get_plugin_version("Groovy") - if version >= pkg_resources.parse_version("2.0.0"): + if plugin_ver >= "2.0.0": secure_groovy_script = XML.SubElement(info, "secureGroovyScript") mapping = [ ("groovy-content", "script", None), @@ -654,17 +652,16 @@ def priority_sorter(registry, xml_parent, data): /../../tests/properties/fixtures/priority_sorter002.yaml :language: yaml """ - plugin_info = registry.get_plugin_info("PrioritySorter") - version = pkg_resources.parse_version(plugin_info.get("version", "0")) + plugin_ver = registry.get_plugin_version("PrioritySorter") - if version >= pkg_resources.parse_version("3.0"): + if plugin_ver >= "3.0": priority_sorter_tag = XML.SubElement( xml_parent, "jenkins.advancedqueue.jobinclusion." "strategy.JobInclusionJobProperty", ) mapping = [("use", "useJobGroup", True), ("priority", "jobGroupName", None)] - elif version >= pkg_resources.parse_version("2.0"): + elif plugin_ver >= "2.0": priority_sorter_tag = XML.SubElement( xml_parent, "jenkins.advancedqueue.priority." "strategy.PriorityJobProperty" ) @@ -954,10 +951,9 @@ def slack(registry, xml_parent, data): """ logger = logging.getLogger(__name__) - plugin_info = registry.get_plugin_info("Slack Notification Plugin") - plugin_ver = pkg_resources.parse_version(plugin_info.get("version", "0")) + plugin_ver = registry.get_plugin_version("Slack Notification Plugin") - if plugin_ver >= pkg_resources.parse_version("2.0"): + if plugin_ver >= "2.0": logger.warning("properties section is not used with plugin version >= 2.0") mapping = ( diff --git a/jenkins_jobs/modules/publishers.py b/jenkins_jobs/modules/publishers.py index ea8038939..34bad619c 100755 --- a/jenkins_jobs/modules/publishers.py +++ b/jenkins_jobs/modules/publishers.py @@ -26,8 +26,6 @@ the build is complete. """ import logging -import pkg_resources -import sys import xml.etree.ElementTree as XML import six @@ -1842,8 +1840,7 @@ def xunit(registry, xml_parent, data): :language: yaml """ - info = registry.get_plugin_info("xunit") - plugin_version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("xunit") logger = logging.getLogger(__name__) xunit = XML.SubElement(xml_parent, "xunit") @@ -1885,7 +1882,7 @@ def xunit(registry, xml_parent, data): # Generate XML for each of the supported framework types # Note: versions 3+ are now using the 'tools' sub-element instead of 'types' - if plugin_version < pkg_resources.parse_version("3.0.0"): + if plugin_ver < "3.0.0": types_name = "types" else: types_name = "tools" @@ -2475,21 +2472,20 @@ def base_email_ext(registry, xml_parent, data, ttype): xml_parent, "hudson.plugins.emailext.plugins.trigger." + ttype ) - info = registry.get_plugin_info("email-ext") - plugin_version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("email-ext") email = XML.SubElement(trigger, "email") - if plugin_version < pkg_resources.parse_version("2.39"): + if plugin_ver < "2.39": XML.SubElement(email, "recipientList").text = "" XML.SubElement(email, "subject").text = "$PROJECT_DEFAULT_SUBJECT" XML.SubElement(email, "body").text = "$PROJECT_DEFAULT_CONTENT" - if plugin_version >= pkg_resources.parse_version("2.39"): + if plugin_ver >= "2.39": XML.SubElement(email, "replyTo").text = "$PROJECT_DEFAULT_REPLYTO" XML.SubElement(email, "contentType").text = "project" if "send-to" in data: recipient_providers = None - if plugin_version < pkg_resources.parse_version("2.39"): + if plugin_ver < "2.39": XML.SubElement(email, "sendToDevelopers").text = str( "developers" in data["send-to"] ).lower() @@ -2572,7 +2568,7 @@ def base_email_ext(registry, xml_parent, data, ttype): "hudson.plugins.emailext.plugins.recipients.UpstreamComitterRecipientProvider", ).text = "" else: - if plugin_version < pkg_resources.parse_version("2.39"): + if plugin_ver < "2.39": XML.SubElement(email, "sendToRequester").text = "false" XML.SubElement(email, "sendToDevelopers").text = "false" XML.SubElement(email, "includeCulprits").text = "false" @@ -2587,7 +2583,7 @@ def base_email_ext(registry, xml_parent, data, ttype): if ttype == "ScriptTrigger": XML.SubElement(trigger, "triggerScript").text = data["trigger-script"] - if plugin_version >= pkg_resources.parse_version("2.39"): + if plugin_ver >= "2.39": mappings = [ ("attachments", "attachmentsPattern", ""), ("attach-build-log", "attachBuildLog", False), @@ -2687,8 +2683,7 @@ def email_ext(registry, xml_parent, data): xml_parent, "hudson.plugins.emailext.ExtendedEmailPublisher" ) - info = registry.get_plugin_info("email-ext") - plugin_version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("email-ext") if "recipients" in data: XML.SubElement(emailext, "recipientList").text = data["recipients"] @@ -2754,7 +2749,7 @@ def email_ext(registry, xml_parent, data): ("reply-to", "replyTo", "$DEFAULT_REPLYTO"), ] - if plugin_version >= pkg_resources.parse_version("2.39"): + if plugin_ver >= "2.39": mappings.append(("from", "from", "")) helpers.convert_mapping_to_xml(emailext, data, mappings, fail_required=True) @@ -3127,13 +3122,11 @@ def groovy_postbuild(registry, xml_parent, data): ) data = {"script": data} # There are incompatible changes, we need to know version - info = registry.get_plugin_info("groovy-postbuild") - # Note: Assume latest version of plugin is preferred config format - version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("groovy-postbuild") # Version specific predicates - matrix_parent_support = version >= pkg_resources.parse_version("1.9") - security_plugin_support = version >= pkg_resources.parse_version("2.0") - extra_classpath_support = version >= pkg_resources.parse_version("1.6") + matrix_parent_support = plugin_ver >= "1.9" + security_plugin_support = plugin_ver >= "2.0" + extra_classpath_support = plugin_ver >= "1.6" root_tag = "org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder" groovy = XML.SubElement(xml_parent, root_tag) @@ -4495,16 +4488,14 @@ def postbuildscript(registry, xml_parent, data): xml_parent, "org.jenkinsci.plugins.postbuildscript.PostBuildScript" ) - info = registry.get_plugin_info("postbuildscript") - # Note: Assume latest version of plugin is preferred config format - version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) - if version >= pkg_resources.parse_version("2.0"): + plugin_ver = registry.get_plugin_version("postbuildscript") + if plugin_ver >= "2.0": pbs_xml = XML.SubElement(pbs_xml, "config") mapping = [("mark-unstable-if-failed", "markBuildUnstable", False)] helpers.convert_mapping_to_xml(pbs_xml, data, mapping, fail_required=True) - if version >= pkg_resources.parse_version("2.0"): + if plugin_ver >= "2.0": def add_execute_on(bs_data, result_xml): valid_values = ("matrix", "axes", "both") @@ -6756,13 +6747,11 @@ def conditional_publisher(registry, xml_parent, data): "dont-run": evaluation_classes_pkg + ".BuildStepRunner$DontRun", } - plugin_info = registry.get_plugin_info("Flexible Publish Plugin") - # Note: Assume latest version of plugin is preferred config format - version = pkg_resources.parse_version(plugin_info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("Flexible Publish Plugin") # Support for MatrixAggregator was added in v0.11 # See JENKINS-14494 - has_matrix_aggregator = version >= pkg_resources.parse_version("0.11") + has_matrix_aggregator = plugin_ver >= "0.11" for cond_action in data: cond_publisher = XML.SubElement(publishers_tag, cond_publisher_tag) @@ -6792,7 +6781,7 @@ def conditional_publisher(registry, xml_parent, data): # XML tag changed from publisher to publisherList in v0.13 # check the plugin version to determine further operations - use_publisher_list = version >= pkg_resources.parse_version("0.13") + use_publisher_list = plugin_ver >= "0.13" if use_publisher_list: action_parent = XML.SubElement(cond_publisher, "publisherList") @@ -7703,11 +7692,7 @@ def slack(registry, xml_parent, data): logger = logging.getLogger(__name__) - plugin_info = registry.get_plugin_info("Slack Notification Plugin") - # Note: Assume latest version of plugin is preferred config format - plugin_ver = pkg_resources.parse_version( - plugin_info.get("version", str(sys.maxsize)) - ) + plugin_ver = registry.get_plugin_version("Slack Notification Plugin") mapping = ( ("team-domain", "teamDomain", ""), @@ -7746,10 +7731,10 @@ def slack(registry, xml_parent, data): slack = XML.SubElement(xml_parent, "jenkins.plugins.slack.SlackNotifier") - if plugin_ver >= pkg_resources.parse_version("2.0"): + if plugin_ver >= "2.0": mapping = mapping + mapping_20 - if plugin_ver < pkg_resources.parse_version("2.0"): + if plugin_ver < "2.0": for yaml_name, _, default_value in mapping: # All arguments that don't have a default value are mandatory for # the plugin to work as intended. diff --git a/jenkins_jobs/modules/triggers.py b/jenkins_jobs/modules/triggers.py index 0b23f7a87..1b421ad76 100644 --- a/jenkins_jobs/modules/triggers.py +++ b/jenkins_jobs/modules/triggers.py @@ -30,9 +30,7 @@ Example:: """ import logging -import pkg_resources import re -import sys import xml.etree.ElementTree as XML import six @@ -198,7 +196,7 @@ def build_gerrit_triggers(xml_parent, data, plugin_ver): ("exclude-private", "excludePrivateState", False), ("exclude-wip", "excludeWipState", False), ] - if plugin_ver >= pkg_resources.parse_version("2.32.0"): + if plugin_ver >= "2.32.0": mapping.append( ( "commit-message-contains-regex", @@ -241,7 +239,7 @@ def build_gerrit_skip_votes(xml_parent, data, plugin_ver): ("unstable", "onUnstable"), ("notbuilt", "onNotBuilt"), ] - if plugin_ver >= pkg_resources.parse_version("2.32.0"): + if plugin_ver >= "2.32.0": outcomes.append(("aborted", "onAborted")) skip_vote_node = XML.SubElement(xml_parent, "skipVote") @@ -252,7 +250,7 @@ def build_gerrit_skip_votes(xml_parent, data, plugin_ver): def build_cancellation_policy(xml_parent, data, plugin_ver): - if plugin_ver >= pkg_resources.parse_version("2.32.0"): + if plugin_ver >= "2.32.0": options = [ ("abort-new-patchsets", "abortNewPatchsets"), ("abort-manual-patchsets", "abortManualPatchsets"), @@ -272,7 +270,7 @@ def build_cancellation_policy(xml_parent, data, plugin_ver): def build_gerrit_parameter_modes(xml_parent, data, plugin_ver): - if plugin_ver < pkg_resources.parse_version("2.18.0"): + if plugin_ver < "2.18.0": for parameter_name in ( "commit-message", "name-and-email", @@ -661,10 +659,7 @@ def gerrit(registry, xml_parent, data): gerrit_handle_legacy_configuration(data) - plugin_info = registry.get_plugin_info("Gerrit Trigger") - plugin_ver = pkg_resources.parse_version( - plugin_info.get("version", str(sys.maxsize)) - ) + plugin_ver = registry.get_plugin_version("Gerrit Trigger") projects = data.get("projects", []) gtrig = XML.SubElement( @@ -797,9 +792,7 @@ def gerrit(registry, xml_parent, data): XML.SubElement(gtrig, "triggerInformationAction").text = str( data.get("trigger-information-action", "") ) - if (plugin_ver >= pkg_resources.parse_version("2.11.0")) and ( - plugin_ver < pkg_resources.parse_version("2.14.0") - ): + if plugin_ver >= "2.11.0" and plugin_ver < "2.14.0": XML.SubElement(gtrig, "allowTriggeringUnreviewedPatches").text = str( data.get("trigger-for-unreviewed-patches", False) ).lower() @@ -848,7 +841,7 @@ def gerrit(registry, xml_parent, data): ), ] - if plugin_ver >= pkg_resources.parse_version("2.31.0"): + if plugin_ver >= "2.31.0": votes.append( ( "gerrit-build-aborted-verified-value", @@ -876,7 +869,7 @@ def gerrit(registry, xml_parent, data): ("custom-url", "customUrl", ""), ("server-name", "serverName", "__ANY__"), ] - if plugin_ver >= pkg_resources.parse_version("2.31.0"): + if plugin_ver >= "2.31.0": message_mappings.append(("aborted-message", "buildAbortedMessage", "")) helpers.convert_mapping_to_xml(gtrig, data, message_mappings, fail_required=True) @@ -1573,14 +1566,9 @@ def gitlab_merge_request(registry, xml_parent, data): xml_parent, "org.jenkinsci.plugins.gitlab." "GitlabBuildTrigger" ) - plugin_info = registry.get_plugin_info("Gitlab Merge Request Builder") + plugin_ver = registry.get_plugin_version("Gitlab Merge Request Builder") - # Note: Assume latest version of plugin is preferred config format - plugin_ver = pkg_resources.parse_version( - plugin_info.get("version", str(sys.maxsize)) - ) - - if plugin_ver >= pkg_resources.parse_version("2.0.0"): + if plugin_ver >= "2.0.0": mapping = [ ("cron", "spec", None), ("project-path", "projectPath", None), @@ -1725,15 +1713,11 @@ def gitlab(registry, xml_parent, data): xml_parent, "com.dabsquared.gitlabjenkins.GitLabPushTrigger" ) - plugin_info = registry.get_plugin_info("GitLab Plugin") - # Note: Assume latest version of plugin is preferred config format - plugin_ver = pkg_resources.parse_version( - plugin_info.get("version", str(sys.maxsize)) - ) + plugin_ver = registry.get_plugin_version("GitLab Plugin") valid_merge_request = ["never", "source", "both"] - if plugin_ver >= pkg_resources.parse_version("1.1.26"): + if plugin_ver >= "1.1.26": mapping = [ ( "trigger-open-merge-request-push", @@ -1749,7 +1733,7 @@ def gitlab(registry, xml_parent, data): ] helpers.convert_mapping_to_xml(gitlab, data, mapping, fail_required=True) - if plugin_ver < pkg_resources.parse_version("1.2.0"): + if plugin_ver < "1.2.0": if data.get("branch-filter-type", "") == "All": data["branch-filter-type"] = "" valid_filters = ["", "NameBasedFilter", "RegexBasedFilter"] diff --git a/jenkins_jobs/modules/wrappers.py b/jenkins_jobs/modules/wrappers.py index 85bc185c9..72c1782b0 100644 --- a/jenkins_jobs/modules/wrappers.py +++ b/jenkins_jobs/modules/wrappers.py @@ -23,8 +23,6 @@ Wrappers can alter the way the build is run as well as the build output. """ import logging -import pkg_resources -import sys import xml.etree.ElementTree as XML from jenkins_jobs.errors import InvalidAttributeError @@ -336,12 +334,9 @@ def timeout(registry, xml_parent, data): prefix = "hudson.plugins.build__timeout." twrapper = XML.SubElement(xml_parent, prefix + "BuildTimeoutWrapper") - plugin_info = registry.get_plugin_info("Build Timeout") - if "version" not in plugin_info: - plugin_info = registry.get_plugin_info("Jenkins build timeout plugin") - version = plugin_info.get("version", None) - if version: - version = pkg_resources.parse_version(version) + plugin_ver = registry.get_plugin_version( + "Build Timeout", "Jenkins build timeout plugin" + ) valid_strategies = [ "absolute", @@ -353,7 +348,7 @@ def timeout(registry, xml_parent, data): # NOTE(toabctl): if we don't know the version assume that we # use a newer version of the plugin - if not version or version >= pkg_resources.parse_version("1.14"): + if plugin_ver >= "1.14": strategy = data.get("type", "absolute") if strategy not in valid_strategies: InvalidAttributeError("type", strategy, valid_strategies) @@ -438,7 +433,7 @@ def timeout(registry, xml_parent, data): all_actions = ["fail", "abort"] actions = [] - if version is not None and version >= pkg_resources.parse_version("1.17"): + if plugin_ver >= "1.17": all_actions.append("abort-and-restart") for action in all_actions: @@ -966,12 +961,9 @@ def rvm_env(registry, xml_parent, data): ro_class = "Jenkins::Plugin::Proxies::BuildWrapper" - plugin_info = registry.get_plugin_info("RVM Plugin") - plugin_ver = pkg_resources.parse_version( - plugin_info.get("version", str(sys.maxsize)) - ) + plugin_ver = registry.get_plugin_version("RVM Plugin") - if plugin_ver >= pkg_resources.parse_version("0.5"): + if plugin_ver >= "0.5": ro_class = "Jenkins::Tasks::BuildWrapperProxy" ro = XML.SubElement(rpo, "ruby-object", {"ruby-class": ro_class, "pluginid": "rvm"}) @@ -1821,8 +1813,7 @@ def pre_scm_buildstep(registry, xml_parent, data): :language: yaml """ # Get plugin information to maintain backwards compatibility - info = registry.get_plugin_info("preSCMbuildstep") - version = pkg_resources.parse_version(info.get("version", "0")) + plugin_ver = registry.get_plugin_version("preSCMbuildstep") bsp = XML.SubElement( xml_parent, "org.jenkinsci.plugins.preSCMbuildstep." "PreSCMBuildStepsWrapper" @@ -1833,7 +1824,7 @@ def pre_scm_buildstep(registry, xml_parent, data): for step in stepList: for edited_node in create_builders(registry, step): bs.append(edited_node) - if version >= pkg_resources.parse_version("0.3") and not isinstance(data, list): + if plugin_ver >= "0.3" and not isinstance(data, list): mapping = [("failOnError", "failOnError", False)] helpers.convert_mapping_to_xml(bsp, data, mapping, fail_required=True) @@ -2049,10 +2040,7 @@ def ssh_agent_credentials(registry, xml_parent, data): logger = logging.getLogger(__name__) - plugin_info = registry.get_plugin_info("SSH Agent Plugin") - plugin_ver = pkg_resources.parse_version( - plugin_info.get("version", str(sys.maxsize)) - ) + plugin_ver = registry.get_plugin_version("SSH Agent Plugin") entry_xml = XML.SubElement( xml_parent, "com.cloudbees.jenkins.plugins.sshagent.SSHAgentBuildWrapper" @@ -2063,7 +2051,7 @@ def ssh_agent_credentials(registry, xml_parent, data): user_list = list() if "users" in data: user_list += data["users"] - if plugin_ver >= pkg_resources.parse_version("1.5.0"): + if plugin_ver >= "1.5.0": user_parent_entry_xml = XML.SubElement(entry_xml, "credentialIds") xml_key = "string" if "user" in data: @@ -2288,8 +2276,8 @@ def nodejs_installator(registry, xml_parent, data): xml_parent, "jenkins.plugins.nodejs." "NodeJSBuildWrapper" ) - version = registry.get_plugin_info("nodejs").get("version", "0") - npm_node.set("plugin", "nodejs@" + version) + plugin_ver = registry.get_plugin_version("nodejs", default="0") + npm_node.set("plugin", "nodejs@" + plugin_ver) mapping = [("name", "nodeJSInstallationName", None)] helpers.convert_mapping_to_xml(npm_node, data, mapping, fail_required=True) @@ -2601,11 +2589,9 @@ def artifactory_generic(registry, xml_parent, data): helpers.artifactory_common_details(details, data) # Get plugin information to maintain backwards compatibility - info = registry.get_plugin_info("artifactory") - # Note: Assume latest version of plugin is preferred config format - version = pkg_resources.parse_version(info.get("version", str(sys.maxsize))) + plugin_ver = registry.get_plugin_version("artifactory") - if version >= pkg_resources.parse_version("2.3.0"): + if plugin_ver >= "2.3.0": deploy_release_repo = XML.SubElement(details, "deployReleaseRepository") mapping = [ ("key-from-text", "keyFromText", ""), diff --git a/jenkins_jobs/registry.py b/jenkins_jobs/registry.py index 391011f23..e9935e590 100644 --- a/jenkins_jobs/registry.py +++ b/jenkins_jobs/registry.py @@ -19,11 +19,13 @@ import inspect import logging import operator import pkg_resources -import re +import sys import types +from pkg_resources.extern.packaging.version import InvalidVersion from six import PY2 +from jenkins.plugins import PluginVersion from jenkins_jobs.errors import JenkinsJobsException from jenkins_jobs.expander import Expander, ParamsExpander from jenkins_jobs.yaml_objects import BaseYamlObject @@ -50,9 +52,10 @@ class ModuleRegistry(object): self._params_expander = ParamsExpander(jjb_config) if plugins_list is None: - self.plugins_dict = {} + self._plugin_version = {} else: - self.plugins_dict = self._get_plugins_info_dict(plugins_list) + # PluginVersion by short and long plugin name. + self._plugin_version = self._get_plugins_versions(plugins_list) for entrypoint in pkg_resources.iter_entry_points(group="jenkins_jobs.modules"): Mod = entrypoint.load() @@ -63,60 +66,38 @@ class ModuleRegistry(object): self.modules_by_component_type[mod.component_type] = entrypoint @staticmethod - def _get_plugins_info_dict(plugins_list): - def mutate_plugin_info(plugin_info): - """ - We perform mutations on a single member of plugin_info here, then - return a dictionary with the longName and shortName of the plugin - mapped to its plugin info dictionary. - """ - version = plugin_info.get("version", "0") - plugin_info["version"] = re.sub( - r"(.*)-(?:SNAPSHOT|BETA).*", r"\g<1>.preview", version - ) + def _get_plugins_versions(plugins_list): + plugin_version = {} - if isinstance( - pkg_resources.parse_version(plugin_info["version"]), - pkg_resources.extern.packaging.version.LegacyVersion, - ): - plugin_info["version"] = plugin_info["version"].replace("-", "+") - if isinstance( - pkg_resources.parse_version(plugin_info["version"]), - pkg_resources.extern.packaging.version.LegacyVersion, - ): - plugin_name = plugin_info.get( - "shortName", plugin_info.get("longName", None) - ) + for plugin_info in plugins_list: + short_name = plugin_info.get("shortName") + long_name = plugin_info.get("longName") + + version = plugin_info["version"] + if version == None: # noqa: E711; should call PluginInfo.__eq__. + # Ensure that plugin_info always has version and it is instance of PluginVersion. + plugin_info["version"] = str(sys.maxsize) + version = plugin_info["version"] + + try: + pkg_resources.parse_version(version) + except InvalidVersion: + plugin_name = short_name or long_name if plugin_name: logger.warning( - "Version %s for plugin %s is being treated as a LegacyVersion" - % (plugin_info["version"], plugin_name) + "Version %s for plugin %s does not conform to PEP440", + version, + plugin_name, ) else: - logger.warning( - "Version %s is being treated as a LegacyVersion" - % plugin_info["version"] - ) + logger.warning("Version %s does not conform to PEP440", version) - aliases = [] - for key in ["longName", "shortName"]: - value = plugin_info.get(key, None) - if value is not None: - aliases.append(value) + if short_name: + plugin_version[short_name] = version + if long_name: + plugin_version[long_name] = version - plugin_info_dict = {} - for name in aliases: - plugin_info_dict[name] = plugin_info - - return plugin_info_dict - - list_of_dicts = [mutate_plugin_info(v) for v in plugins_list] - - plugins_info_dict = {} - for d in list_of_dicts: - plugins_info_dict.update(d) - - return plugins_info_dict + return plugin_version @staticmethod def _filter_kwargs(func, **kwargs): @@ -126,17 +107,21 @@ class ModuleRegistry(object): del kwargs[name] return kwargs - def get_plugin_info(self, plugin_name): - """Provide information about plugins within a module's impl of Base.gen_xml. + def get_plugin_version(self, plugin_name, alt_plugin_name=None, default=None): + """Provide plugin version to be used from a module's impl of Base.gen_xml. - The return value is a dictionary with data obtained directly from a - running Jenkins instance. + The return value is a plugin version obtained directly from a running + Jenkins instance. This allows module authors to differentiate generated XML output based - on information such as specific plugin versions. + on it. :arg str plugin_name: Either the shortName or longName of a plugin - as see in a query that looks like: + as seen in a query that looks like: ``http:///pluginManager/api/json?pretty&depth=2`` + :arg str alt_plugin_name: Alternative plugin name. Used if plugin_name + is missing in plugin list. + :arg str default: Default value. Used if plugin name is missing in + plugin list. During a 'test' run, it is possible to override JJB's query to a live Jenkins instance by passing it a path to a file containing a YAML list @@ -151,7 +136,19 @@ class ModuleRegistry(object): .. literalinclude:: /../../tests/cmd/fixtures/plugins-info.yaml """ - return self.plugins_dict.get(plugin_name, {}) + try: + return self._plugin_version[plugin_name] + except KeyError: + pass + if alt_plugin_name: + try: + return self._plugin_version[alt_plugin_name] + except KeyError: + pass + if default is not None: + return PluginVersion(default) + # Assume latest version of plugin is preferred config format. + return PluginVersion(str(sys.maxsize)) def registerHandler(self, category, name, method): cat_dict = self.handlers.get(category, {}) diff --git a/requirements.txt b/requirements.txt index 1764e335b..b225a6a75 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,11 @@ # The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. -setuptools<=65.7.0 six>=1.9.0 # MIT PyYAML>=3.13 # MIT pbr>=1.8 # Apache-2.0 stevedore>=1.17.1,<2; python_version < '3.0' # Apache-2.0 stevedore>=1.17.1; python_version >= '3.0' # Apache-2.0 -python-jenkins>=0.4.15 +python-jenkins>=1.8.2 fasteners Jinja2 diff --git a/tests/conftest.py b/tests/conftest.py index 32b267fd7..372bda619 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,7 @@ from pathlib import Path import pytest import yaml +from jenkins.plugins import Plugin from jenkins_jobs.alphanum import AlphanumSort from jenkins_jobs.config import JJBConfig from jenkins_jobs.loader import Loader @@ -72,7 +73,8 @@ def input(scenario, jjb_config): def plugins_info(scenario): if not scenario.plugins_info_path.exists(): return None - return yaml.safe_load(scenario.plugins_info_path.read_text()) + plugin_dict_list = yaml.safe_load(scenario.plugins_info_path.read_text()) + return [Plugin(**plugin_dict) for plugin_dict in plugin_dict_list] @pytest.fixture diff --git a/tests/hipchat/fixtures/hipchat002.plugins_info.yaml b/tests/hipchat/fixtures/hipchat002.plugins_info.yaml new file mode 100644 index 000000000..539d09eef --- /dev/null +++ b/tests/hipchat/fixtures/hipchat002.plugins_info.yaml @@ -0,0 +1,3 @@ +- longName: 'Jenkins HipChat Plugin' + shortName: 'hipchat' + version: "0.1" diff --git a/tests/jsonparser/fixtures/complete001.xml b/tests/jsonparser/fixtures/complete001.xml index 1e00b88df..2e52c1368 100644 --- a/tests/jsonparser/fixtures/complete001.xml +++ b/tests/jsonparser/fixtures/complete001.xml @@ -155,6 +155,7 @@ echo "Doing somethin cool with zsh" + false file1,file2*.txt diff --git a/tests/moduleregistry/test_moduleregistry.py b/tests/moduleregistry/test_moduleregistry.py index 3be1967e0..db9ab8658 100644 --- a/tests/moduleregistry/test_moduleregistry.py +++ b/tests/moduleregistry/test_moduleregistry.py @@ -1,9 +1,10 @@ -import pkg_resources +import sys from collections import namedtuple from operator import attrgetter import pytest +from jenkins.plugins import Plugin, PluginVersion from jenkins_jobs.config import JJBConfig from jenkins_jobs.registry import ModuleRegistry @@ -35,6 +36,18 @@ scenarios = [ Scenario("s17", v1="1.0.1-1.v1", op="__lt__", v2="1.0.2"), Scenario("s18", v1="1.0.2-1.v1", op="__gt__", v2="1.0.1"), Scenario("s19", v1="1.0.2-1.v1", op="__gt__", v2="1.0.1-2"), + # 'Groovy' plugin in 'inject' property. + Scenario("s20", v1="453.vcdb_a_c5c99890", op="__ge__", v2="2.0.0"), + # 'postbuildscript' plugin in 'postbuildscript' publisher. + Scenario("s21", v1="3.2.0-460.va_fda_0fa_26720", op="__ge__", v2="2.0"), + # Same, from story: 2009943. + Scenario("s22", v1="3.1.0-375.v3db_cd92485e1", op="__ge__", v2="2.0"), + # 'Slack Notification Plugin' in 'slack' publisher, from story: 2009819. + Scenario("s23", v1="602.v0da_f7458945d", op="__ge__", v2="2.0"), + # 'preSCMbuildstep' plugin in 'pre_scm_buildstep' wrapper. + Scenario("s24", v1="44.v6ef4fd97f56e", op="__ge__", v2="0.3"), + # 'SSH Agent Plugin' plugin in 'ssh_agent_credentials' wrapper. + Scenario("s25", v1="295.v9ca_a_1c7cc3a_a_", op="__ge__", v2="1.5.0"), ] @@ -66,65 +79,69 @@ def registry(config, scenario): "version": scenario.v1, }, ] - return ModuleRegistry(config, plugin_info) + return ModuleRegistry(config, [Plugin(**d) for d in plugin_info]) -def test_get_plugin_info_dict(registry): +def test_get_plugin_version_by_short_name(scenario, registry): """ - The goal of this test is to validate that the plugin_info returned by - ModuleRegistry.get_plugin_info is a dictionary whose key 'shortName' is - the same value as the string argument passed to - ModuleRegistry.get_plugin_info. + Plugin version should be available by it's short name """ plugin_name = "JankyPlugin1" - plugin_info = registry.get_plugin_info(plugin_name) + version = registry.get_plugin_version(plugin_name) - assert isinstance(plugin_info, dict) - assert plugin_info["shortName"] == plugin_name + assert isinstance(version, PluginVersion) + assert version == scenario.v1 -def test_get_plugin_info_dict_using_longName(registry): +def test_get_plugin_version_by_long_name(scenario, registry): """ - The goal of this test is to validate that the plugin_info returned by - ModuleRegistry.get_plugin_info is a dictionary whose key 'longName' is - the same value as the string argument passed to - ModuleRegistry.get_plugin_info. + Plugin version should be available by it's long name """ - plugin_name = "Blah Blah Blah Plugin" - plugin_info = registry.get_plugin_info(plugin_name) + plugin_name = "Not A Real Plugin" + version = registry.get_plugin_version(plugin_name) - assert isinstance(plugin_info, dict) - assert plugin_info["longName"] == plugin_name + assert isinstance(version, PluginVersion) + assert version == scenario.v1 -def test_get_plugin_info_dict_no_plugin(registry): +def test_get_plugin_version_by_alternative_name(scenario, registry): + version = registry.get_plugin_version("Non-existent name", "Not A Real Plugin") + assert version == scenario.v1 + + +def test_get_plugin_version_default_value(registry): + version = registry.get_plugin_version("Non-existent name", default="1.2.3") + assert isinstance(version, PluginVersion) + assert version == "1.2.3" + + +def test_get_plugin_version_for_missing_plugin(registry): """ The goal of this test case is to validate the behavior of - ModuleRegistry.get_plugin_info when the given plugin cannot be found in + ModuleRegistry.get_plugin_version when the given plugin cannot be found in ModuleRegistry's internal representation of the plugins_info. """ plugin_name = "PluginDoesNotExist" - plugin_info = registry.get_plugin_info(plugin_name) + version = registry.get_plugin_version(plugin_name) - assert isinstance(plugin_info, dict) - assert plugin_info == {} + assert isinstance(version, PluginVersion) + assert version == str(sys.maxsize) -def test_get_plugin_info_dict_no_version(registry): +def test_get_plugin_version_for_missing_version(registry): """ The goal of this test case is to validate the behavior of - ModuleRegistry.get_plugin_info when the given plugin shortName returns + ModuleRegistry.get_plugin_version when the given plugin shortName returns plugin_info dict that has no version string. In a sane world where plugin frameworks like Jenkins' are sane this should never happen, but I am including this test and the corresponding default behavior because, well, it's Jenkins. """ plugin_name = "HerpDerpPlugin" - plugin_info = registry.get_plugin_info(plugin_name) + version = registry.get_plugin_version(plugin_name) - assert isinstance(plugin_info, dict) - assert plugin_info["shortName"] == plugin_name - assert plugin_info["version"] == "0" + assert isinstance(version, PluginVersion) + assert version == str(sys.maxsize) def test_plugin_version_comparison(registry, scenario): @@ -134,13 +151,12 @@ def test_plugin_version_comparison(registry, scenario): where 'op' is the equality operator defined for the scenario. """ plugin_name = "JankyPlugin1" - plugin_info = registry.get_plugin_info(plugin_name) - v1 = plugin_info.get("version") + v1 = registry.get_plugin_version(plugin_name) - op = getattr(pkg_resources.parse_version(v1), scenario.op) - test = op(pkg_resources.parse_version(scenario.v2)) + op = getattr(v1, scenario.op) + test = op(scenario.v2) assert test, ( - f"Unexpectedly found {v1} {scenario.v2} {scenario.op} == False" + f"Unexpectedly found {v1} {scenario.op} {scenario.v2} == False" " when comparing versions!" ) diff --git a/tests/yamlparser/job_fixtures/complete001.xml b/tests/yamlparser/job_fixtures/complete001.xml index 92cef9bd1..7246737e9 100644 --- a/tests/yamlparser/job_fixtures/complete001.xml +++ b/tests/yamlparser/job_fixtures/complete001.xml @@ -153,6 +153,7 @@ echo "Doing somethin cool with zsh" + false file1,file2*.txt diff --git a/tests/yamlparser/job_fixtures/include-raw-escape001.xml b/tests/yamlparser/job_fixtures/include-raw-escape001.xml index 060b5cabe..2f4cd343a 100644 --- a/tests/yamlparser/job_fixtures/include-raw-escape001.xml +++ b/tests/yamlparser/job_fixtures/include-raw-escape001.xml @@ -14,6 +14,9 @@ false + + false + true true diff --git a/tests/yamlparser/job_fixtures/include-raw001.xml b/tests/yamlparser/job_fixtures/include-raw001.xml index 7ec205f11..665973b3e 100644 --- a/tests/yamlparser/job_fixtures/include-raw001.xml +++ b/tests/yamlparser/job_fixtures/include-raw001.xml @@ -11,6 +11,9 @@ false + + false + true true @@ -55,6 +58,7 @@ echo "Doing somethin cool with zsh" + false file1,file2*.txt diff --git a/tests/yamlparser/job_fixtures/include-rawunicode001.xml b/tests/yamlparser/job_fixtures/include-rawunicode001.xml index f06dfb72d..1c91ed6d5 100644 --- a/tests/yamlparser/job_fixtures/include-rawunicode001.xml +++ b/tests/yamlparser/job_fixtures/include-rawunicode001.xml @@ -20,6 +20,7 @@ echo "Unicode! ☃" + false diff --git a/tests/yamlparser/job_fixtures/include001.xml b/tests/yamlparser/job_fixtures/include001.xml index ec833ab2b..e4da04b76 100644 --- a/tests/yamlparser/job_fixtures/include001.xml +++ b/tests/yamlparser/job_fixtures/include001.xml @@ -11,6 +11,9 @@ false + + false + true true @@ -55,6 +58,7 @@ echo "Doing somethin cool with zsh" + false file1,file2*.txt diff --git a/tests/yamlparser/job_fixtures/string_join.xml b/tests/yamlparser/job_fixtures/string_join.xml index 2abfaa2d7..4faec0752 100644 --- a/tests/yamlparser/job_fixtures/string_join.xml +++ b/tests/yamlparser/job_fixtures/string_join.xml @@ -13,6 +13,9 @@ FILE_LIST=/path/to/file1,/path/to/file2,/path/to/file3,/path/to/file4,/path/to/file5,/path/to/file6,/path/to/file7,/path/to/file8,/path/to/file9,/path/to/file10,/path/to/file11,/path/to/file12,/path/to/file13,/path/to/file14,/path/to/file15,/path/to/file16,/path/to/file17,/path/to/file18,/path/to/file19,/path/to/file20 false + + false + true true @@ -48,6 +51,9 @@ echo "${INPUT_DATA}" FILE_LIST=/another/different/path/to/file1,/another/different/path/to/file2,/another/different/path/to/file3,/another/different/path/to/file4,/another/different/path/to/file5,/another/different/path/to/file6,/another/different/path/to/file7,/another/different/path/to/file8,/another/different/path/to/file9,/another/different/path/to/file10,/another/different/path/to/file11,/another/different/path/to/file12,/another/different/path/to/file13,/another/different/path/to/file14,/another/different/path/to/file15,/another/different/path/to/file16,/another/different/path/to/file17,/another/different/path/to/file18,/another/different/path/to/file19,/another/different/path/to/file20 false + + false + true true diff --git a/tests/yamlparser/job_fixtures/unicode001.xml b/tests/yamlparser/job_fixtures/unicode001.xml index f06dfb72d..1c91ed6d5 100644 --- a/tests/yamlparser/job_fixtures/unicode001.xml +++ b/tests/yamlparser/job_fixtures/unicode001.xml @@ -20,6 +20,7 @@ echo "Unicode! ☃" + false