diff --git a/build.gradle b/build.gradle index b11a3fc..7a608e5 100644 --- a/build.gradle +++ b/build.gradle @@ -30,8 +30,8 @@ apply plugin: 'eclipse' apply plugin: 'idea' java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 } defaultTasks 'clean','build' @@ -40,8 +40,8 @@ group = 'com.github.kdebisschop' ext.rundeckPluginVersion = '1.2' ext.pluginClassNames='com.bioraft.rundeck.nexus.Nexus3OptionProvider' -ext.pluginName = 'Nexus3 Option Values Porvider' -ext.pluginDescription = 'Fetches and filters assests from a Nexus3 repository' +ext.pluginName = 'Nexus3 Option Values Provider' +ext.pluginDescription = 'Fetches and filters assets from a Nexus3 repository' scmVersion { ignoreUncommittedChanges = true @@ -65,7 +65,6 @@ scmVersion { project.version = scmVersion.version repositories { - mavenLocal() mavenCentral() } @@ -86,9 +85,10 @@ gradle.projectsEvaluated { } dependencies { - implementation 'org.rundeck:rundeck-core:3.4.10-20220118' + implementation 'org.rundeck:rundeck-core:5.4.0-20240618' + implementation 'org.apache.maven:maven-artifact:3.9.8' - testImplementation group: 'junit', name: 'junit', version:'4.12' + testImplementation group: 'junit', name: 'junit', version: '4.13.1' testImplementation ( 'org.mockito:mockito-all:1.10.19', diff --git a/src/main/java/com/bioraft/rundeck/nexus/BranchOrVersion.java b/src/main/java/com/bioraft/rundeck/nexus/BranchOrVersion.java index 0b90bc1..48ac3cf 100644 --- a/src/main/java/com/bioraft/rundeck/nexus/BranchOrVersion.java +++ b/src/main/java/com/bioraft/rundeck/nexus/BranchOrVersion.java @@ -15,38 +15,34 @@ */ package com.bioraft.rundeck.nexus; -import java.util.Arrays; -import java.util.Iterator; - -import org.apache.commons.lang.builder.CompareToBuilder; +import org.apache.maven.artifact.versioning.ComparableVersion; /** * Puts branches or versions followed by a build designator into a uniform structure. - * - * Expands three-part semantic versions to handle cases where there are more - * than 3 parts and where build specifier may be attached by "_" or "+". (Although - * it would really just be better if people only used semantic versions.) - * - * Also puts branches into a similar structure. In particular, branches like + * + *

Parses versions and branches where there is a build specifier attached by "-", + * "_" or "+". + * + *

Also puts branches into a similar structure. In particular, branches like * ISSUE-1234-bug-description will be sorted numerically. - * - * In particular, the algorithm assumes each componentVersion or docker image tag + * + *

In particular, the algorithm assumes each componentVersion or docker image tag * is composed of a version or branch followed by a build designator. The build * designator is first extracted by looking for a part of the componentVersion * after either "-", "_", or "+". If there is more than one [_+-] the match looks * for the last one. - * - * The part before the build designator (and separator) is considered the version + * + *

The part before the build designator (and separator) is considered the version * or branch. If there are two consecutive integers separated by a decimal, then it - * is considered a version specifier. Otherwise it is considered a branch name. In + * is considered a version specifier. Otherwise, it is considered a branch name. In * either case, it is split into parts at each period (i.e., "."). - * - * Each part of the versionOrBranch and the build specifier are considered in 3 parts: + * + *

Each part of the versionOrBranch and the build specifier are considered in 3 parts: * - Zero or more non-numeric initial characters * - Zero or more integers * - Zero or more concluding characters that may be a mix of integers and non-integers - * - * These fields in order are used to sort the assets. + * + *

These fields in order are used to sort the assets. * * @author Karl DeBisschop * @since 2019-12-26 @@ -55,11 +51,11 @@ public class BranchOrVersion { public static final String BUILD_SEPARATOR_REGEX = "[_+-]"; - String artifactId; - String versionOrBranch; - String build; - String sep; - String[] parts; + private final String artifactId; + private final String versionOrBranch; + private String build; + private final String componentVersion; + private final String comparator; public String getArtifactId() { return artifactId; @@ -75,21 +71,24 @@ public String getVersion() { public BranchOrVersion(String path) { artifactId = component(path); - String tag = tag(path); - if (tag.matches("^.+" + BUILD_SEPARATOR_REGEX + "[a-zA-Z0-9]+$")) { - build = tag.replaceFirst("^.+" + BUILD_SEPARATOR_REGEX + "([a-zA-Z0-9]+)$", "$1"); - versionOrBranch = tag.replaceFirst("^(.+)" + BUILD_SEPARATOR_REGEX + build + "$", "$1"); - sep = tag.replaceFirst("^.+(" + BUILD_SEPARATOR_REGEX + ")" + build + "$", "$1"); + componentVersion = tag(path); + String sep; + if (componentVersion.matches("^.+" + BUILD_SEPARATOR_REGEX + "[a-zA-Z0-9_-]+$")) { + build = componentVersion.replaceFirst("^.+" + BUILD_SEPARATOR_REGEX + "([a-zA-Z0-9_-]+)$", "$1"); + versionOrBranch = componentVersion.replaceFirst("^(.+)" + BUILD_SEPARATOR_REGEX + build + "$", "$1"); + sep = componentVersion.replaceFirst("^.+(" + BUILD_SEPARATOR_REGEX + ")" + build + "$", "$1"); } else { build = ""; - versionOrBranch = tag; + versionOrBranch = componentVersion; sep = ""; } - this.parts = versionOrBranch.split("[.]"); - } - - public Iterator getIterator() { - return Arrays.asList(parts).iterator(); + if (componentVersion.matches("^rc(\\d+[.].*)")) { + build = build + "rc"; + if (sep.isEmpty()) { + sep = "-"; + } + } + comparator = componentVersion.replaceFirst("^(v|rc)(\\d+[.].*)", "$2") + sep + build; } /** @@ -97,13 +96,12 @@ public Iterator getIterator() { * Anything else is considered to be a branch. */ public boolean isVersion() { - return versionOrBranch.matches(".*[0-9]+[.][0-9]+.*"); + return versionOrBranch.matches(".*\\d+[.]\\d+.*"); } /** - * Use array of parts to do comparison so it can compare regardless of number of - * parts in a version specifier. - * + * Decorate maven ComparableVersion to handle a few edge cases. + * * @param other The object to compare against. * @return Return 0 if equal, 1 if this is greater, -1 if that is greater. */ @@ -112,50 +110,13 @@ public int compareTo(Object other) { return 1; } BranchOrVersion that = (BranchOrVersion) other; - CompareToBuilder comparator = new CompareToBuilder(); - Iterator thatIterator = that.getIterator(); - Iterator thisIterator = getIterator(); - while (thatIterator.hasNext()) { - if (!thisIterator.hasNext()) { - if (comparator.toComparison() == 0) { - return -1; - } else { - break; - } - } - String thisOne = thisIterator.next(); - String thatOne = thatIterator.next(); - comparator.append(partOne(thisOne), partOne(thatOne)); - comparator.append(partTwo(thisOne), partTwo(thatOne)); - comparator.append(partThree(thisOne), partThree(thatOne)); - } - if (comparator.toComparison() == 0 && thisIterator.hasNext()) { - return 1; - } - comparator.append(partOne(this.build), partOne(that.build)); - comparator.append(partTwo(this.build), partTwo(that.build)); - comparator.append(partThree(this.build), partThree(that.build)); - return comparator.toComparison(); - } - - private String partOne(String string) { - return string.replaceFirst("^([^0-9]*).*$", "$1"); - } - - private Integer partTwo(String string) { - String integerPart = string.replaceFirst("^[^0-9]*([0-9]*).*$", "$1"); - if (integerPart.length() == 0) { - return 0; - } - return Integer.parseInt(integerPart); - } - - private String partThree(String string) { - return string.replaceFirst("^[^0-9]*[0-9]*", ""); + ComparableVersion thisVersion = new ComparableVersion(comparator); + ComparableVersion thatVersion = new ComparableVersion(that.comparator); + return Integer.signum(thisVersion.compareTo(thatVersion)); } public String toString() { - return artifactId + ":" + String.join(".", parts) + sep + build; + return artifactId + ":" + componentVersion; } /** @@ -168,7 +129,7 @@ private String component(String path) { /** * Extracts tag part of a path. The tag (or componentVersion in the Nexus query) * can reflect either a branch name or a semantic version. Either branches or - * versions may be suffixed by a build specified, which can be numeric or + * versions may be suffixed by a build specifier, which can be numeric or * string-valued. */ private String tag(String path) { diff --git a/src/main/java/com/bioraft/rundeck/nexus/Nexus3OptionProvider.java b/src/main/java/com/bioraft/rundeck/nexus/Nexus3OptionProvider.java index 35439ac..42f22d4 100644 --- a/src/main/java/com/bioraft/rundeck/nexus/Nexus3OptionProvider.java +++ b/src/main/java/com/bioraft/rundeck/nexus/Nexus3OptionProvider.java @@ -32,7 +32,7 @@ public class Nexus3OptionProvider implements OptionValuesPlugin { public static final String PLUGIN_NAME = "Nexus3OptionProvider"; - private OkHttpClient client; + private final OkHttpClient client; @PluginProperty(title = "Endpoint scheme", description = "Nexus server scheme", required = true, defaultValue = "https", scope = PropertyScope.Project) private String endpointScheme; @@ -89,7 +89,7 @@ public List getOptionValues(Map configuration) { private void setVariable(Map configuration, String variableName, String variable) { if (configuration.containsKey(variableName)) { config.put(variableName, configuration.get(variableName).toString()); - } else if (variable != null && variable.length() > 0) { + } else if (variable != null && !variable.isEmpty()) { config.put(variableName, variable); } } diff --git a/src/main/java/com/bioraft/rundeck/nexus/OptionProviderImpl.java b/src/main/java/com/bioraft/rundeck/nexus/OptionProviderImpl.java index 8b4ac06..be47702 100644 --- a/src/main/java/com/bioraft/rundeck/nexus/OptionProviderImpl.java +++ b/src/main/java/com/bioraft/rundeck/nexus/OptionProviderImpl.java @@ -31,7 +31,7 @@ * Expands three-part semantic versions to handle cases where there are more * than 3 parts and where build specifier may be attached by "_" or "+". * - * It would really just be better if people only used semantic versions. + *

It would really just be better if people only used semantic versions. * * @author Karl DeBisschop * @since 2019-12-26 @@ -40,7 +40,7 @@ public class OptionProviderImpl { static final String CONTINUATION_TOKEN = "continuationToken"; - private OkHttpClient client; + private final OkHttpClient client; private Map config; @@ -105,12 +105,13 @@ public List getOptionValues(Map config) { /** * Update the Branches Map. + * * @param current The branch or version object we are considering. * @param versionOrBuild The version or build of the object we are considering. */ private void updateBranchOrVersionMap(BranchOrVersion current, String versionOrBuild, Map map) { // If we are already tracking the branch, check to ensure this is newer before saving. - // Otherwise, it is a new branc to us so start tracking it. + // Otherwise, it is a new branch to us so start tracking it. if (map.containsKey(versionOrBuild)) { if (current.compareTo(map.get(versionOrBuild)) > 0) { map.put(versionOrBuild, current); @@ -135,10 +136,7 @@ static SortedSet> entriesSortedByValues( } /** - * Perform the Nexus API request. - * - * This is really just syntactic sugar for the initial request (with no - * continuation token). But it adds some clarity. + * Perform the initial Nexus API request. * * @return An ArrayList of path strings. */ @@ -149,7 +147,7 @@ private ArrayList nexusSearch() { /** * Perform the Nexus API request, including continuation for larger result sets. * - * Since all we need to prepare the option list is path, extract that from the + *

Since all we need to prepare the option list is path, extract that from the * JsonNode and return just a list of strings in the interest of conserving * system resources. * @@ -179,7 +177,7 @@ private ArrayList nexusSearch(String continuationToken) { return new ArrayList<>(); } json = body.string(); - if (json.length() == 0) { + if (json.isEmpty()) { return new ArrayList<>(); } } catch (NullPointerException | IOException e) { @@ -198,7 +196,7 @@ private ArrayList nexusSearch(String continuationToken) { } if (tree.has(CONTINUATION_TOKEN)) { String token = tree.path(CONTINUATION_TOKEN).asText(); - if (token.length() > 0) { + if (!token.isEmpty()) { ArrayList nextItems = nexusSearch(token); itemList.addAll(nextItems); } @@ -225,8 +223,8 @@ private HttpUrl.Builder buildUrl(String endpoint, String continuationToken) { /** * Provides a means of informing users that the Nexus host still needs to be * configured. - * - * All methods are straightforward implementations of the interface. + * + *

All methods are straightforward implementations of the interface. */ static class ErrorOptionValue implements OptionValue { String name; diff --git a/src/test/java/com/bioraft/rundeck/nexus/BranchOrVersionTest.java b/src/test/java/com/bioraft/rundeck/nexus/BranchOrVersionTest.java index 3dd56fe..eb61e3f 100644 --- a/src/test/java/com/bioraft/rundeck/nexus/BranchOrVersionTest.java +++ b/src/test/java/com/bioraft/rundeck/nexus/BranchOrVersionTest.java @@ -45,40 +45,36 @@ public void testOne() { @Test public void testComparison() { - BranchOrVersion testSubject; - final String COMPONENT = "COMP"; + BranchOrVersion version; + final String COMPONENT = "example.org/component"; final String EMPTY = ""; - final String DASH = "-"; - testSubject = subject(path(COMPONENT, "1.2.3", DASH, "3")); - - assertEquals(0, testSubject.compareTo(subject(path(COMPONENT, "1.2.3", DASH, "3")))); - - // 1.2.3-3 is greater than null - assertEquals(1, testSubject.compareTo(null)); - // 1.2.3-3 is greater than 1.2.3-2 - assertEquals(1, testSubject.compareTo(subject(path(COMPONENT, "1.2.3", DASH, "2")))); - // 1.2.3-3 is greater than 1.2.2-3 - assertEquals(1, testSubject.compareTo(subject(path(COMPONENT, "1.2.2", DASH, "3")))); - - // 1.2.3-3 is less than 1.2.3-4 - assertEquals(-1, testSubject.compareTo(subject(path(COMPONENT, "1.2.3", DASH, "4")))); - // 1.2.3-3 is less than 1.2.3-14 - assertEquals(-1, testSubject.compareTo(subject(path(COMPONENT, "1.2.3", DASH, "14")))); - // 1.2.3-3 is less than 2.2.3-1 - assertEquals(-1, testSubject.compareTo(subject(path(COMPONENT, "2.2.3", DASH, "1")))); - - // 1.2.3-3 is greater than 1.2-3 - assertEquals(1, testSubject.compareTo(subject(path(COMPONENT, "1.2", DASH, "3")))); - // 1.2.3-3 is greater than 1.2.2.2-3 - assertEquals(1, testSubject.compareTo(subject(path(COMPONENT, "1.2.2.2", DASH, "3")))); - // 1.2.3-3 is less than 1.2.3.1-3 - assertEquals(-1, testSubject.compareTo(subject(path(COMPONENT, "1.2.3.1", DASH, "3")))); + version = subject("1.2.3-3"); + assertEquals(1, version.compareTo(null)); + assertEquals(1, version.compareTo(subject("1.2.3"))); + assertEquals(1, version.compareTo(subject("1.2.3-2"))); + assertEquals(1, version.compareTo(subject("1.2.2-3"))); + assertEquals(1, version.compareTo(subject("v1.2.2-3"))); + assertEquals(1, version.compareTo(subject("1.2-3"))); + assertEquals(1, version.compareTo(subject("1.2.2.2-3"))); + assertEquals(1, version.compareTo(subject("rc1.2.3-3"))); + assertEquals(0, version.compareTo(subject("v1.2.3-3"))); + assertEquals(0, version.compareTo(subject("1.2.3-3"))); + assertEquals(-1, version.compareTo(subject("1.2.3.1-3"))); + assertEquals(-1, version.compareTo(subject("1.2.3-4"))); + assertEquals(-1, version.compareTo(subject("1.2.3-14"))); + assertEquals(-1, version.compareTo(subject("2.2.3-1"))); + assertEquals(-1, version.compareTo(subject("v1.2.4-3"))); + assertEquals(-1, version.compareTo(subject("rc1.2.4-3"))); + assertEquals(-1, version.compareTo(subject("20240720.1-3"))); + assertEquals(-1, version.compareTo(subject("20240720.1"))); + + version = subject("1.2.3-3"); + assertEquals(1, version.compareTo(subject("rc1.2.2-4"))); + assertEquals(-1, version.compareTo(subject("20240720.1"))); assertEquals(0, subject(COMPONENT).compareTo(subject(COMPONENT))); assertEquals(0, subject(EMPTY).compareTo(subject(EMPTY))); - - } public void runTest(String component, String version, String separator, String build) { @@ -89,11 +85,15 @@ public void runTest(String component, String version, String separator, String b assertEquals(build, subject.getBuild()); } - public String path(String component, String version, String separator, String build) { - return "v2/" + component + "/manifests/" + version + separator + build; + public BranchOrVersion subject(String componentVersion) { + return new BranchOrVersion("v2/component/manifests/" + componentVersion); + } + + public BranchOrVersion subject(String componentVersion, String build) { + return new BranchOrVersion("v2/component/manifests/" + componentVersion + "-" + build); } - public BranchOrVersion subject(String path) { - return new BranchOrVersion(path); + public BranchOrVersion subject(String componentVersion, String separator, String build) { + return new BranchOrVersion("v2/component/manifests/" + componentVersion + separator + build); } } \ No newline at end of file diff --git a/src/test/java/com/bioraft/rundeck/nexus/Nexus3OptionProviderTest.java b/src/test/java/com/bioraft/rundeck/nexus/Nexus3OptionProviderTest.java index 55c1781..5203fdf 100644 --- a/src/test/java/com/bioraft/rundeck/nexus/Nexus3OptionProviderTest.java +++ b/src/test/java/com/bioraft/rundeck/nexus/Nexus3OptionProviderTest.java @@ -269,17 +269,18 @@ public void returnsFourItemsForOneBranchAndTwoReleases() throws IOException { public void returnsFourItemsForOneBranchAndTwoSemanticReleases() throws IOException { when(client.newCall(any())).thenReturn(call); String json = "{\"items\":[" + item("sprint-11_3") + "," + item("sprint-11_4") + "," + item("v1.2.3_4") + "," - + item("v1.2.2_1") + "," + item("v1.2.3_3") + "," + item("v1.2.2_2") + "]}"; + + item("v1.2.2_1") + "," + item("v1.2.3_3") + "," + item("v1.2.2_2") + "," + item("20240720.0") + "]}"; when(call.execute()).thenReturn(response(json)); Nexus3OptionProvider provider = new Nexus3OptionProvider(client); List options = provider.getOptionValues(configuration); - assertEquals(4, options.size()); - assertEquals("COMP_NAME:v1.2.3_4", options.get(0).getName()); + assertEquals(5, options.size()); + assertEquals("COMP_NAME:20240720.0", options.get(0).getName()); assertEquals("COMP_NAME:sprint-11_4", options.get(1).getName()); assertEquals("COMP_NAME:v1.2.2_2", options.get(2).getName()); assertEquals("COMP_NAME:v1.2.3_4", options.get(3).getName()); + assertEquals("COMP_NAME:20240720.0", options.get(4).getName()); } @Test @@ -302,15 +303,16 @@ public void returnsFourItemsForOneBranchAndTwoBareSemanticReleases() throws IOEx @Test public void handlesTagsWithoutBuildSpecifier() throws IOException { when(client.newCall(any())).thenReturn(call); - String json = "{\"items\":[" + item("v141.1") + "," + item("rc141.1raft4711a") + "]}"; + String json = "{\"items\":[" + item("v141.1") + "," + item("rc141.1") + "]}"; when(call.execute()).thenReturn(response(json)); Nexus3OptionProvider provider = new Nexus3OptionProvider(client); List options = provider.getOptionValues(configuration); assertEquals(3, options.size()); - assertEquals("COMP_NAME:rc141.1raft4711a", options.get(1).getName()); assertEquals("COMP_NAME:v141.1", options.get(0).getName()); + assertEquals("COMP_NAME:rc141.1", options.get(1).getName()); + assertEquals("COMP_NAME:v141.1", options.get(2).getName()); } @Test @@ -334,9 +336,9 @@ public void sortsBuildNumbersNumerically() throws IOException { public void sortsIssuesNumerically() throws IOException { when(client.newCall(any())).thenReturn(call); String json = "{\"items\":[" + item("sprint_11-13") + "," + item("sprint_11-4") + "," + item("1.2.3-4") + "," - + item("1.2.2-21") + "," + item("1.2.3-11") + "," + item("ISSUE-234-one-issue-2") + "," - + item("ISSUE-234-one-issue-12") + "," + item("ISSUE-1000-another-issue-27") + "," - + item("ISSUE-1000-another-issue-13") + "," + item("1.2.2-2") + "]}"; + + item("1.2.2-21") + "," + item("1.2.3-11") + "," + item("ISSUE-1000-another-issue-27") + "," + + item("ISSUE-1000-another-issue-13") + "," + item("ISSUE-234-one-issue-2") + "," + + item("ISSUE-234-one-issue-12") + "," + item("1.2.2-2") + "]}"; when(call.execute()).thenReturn(response(json)); Nexus3OptionProvider provider = new Nexus3OptionProvider(client);