diff --git a/README.md b/README.md index ae4f573..bb2b071 100644 --- a/README.md +++ b/README.md @@ -56,3 +56,7 @@ Additional Parameters: * `-Dgithub.commitish=release/1.0.0` allows to specify a commitsh The plugin is available on Maven central + +## Note on the GitHub API endpoints +The endpoint for GitHub API is inferred from `` connection string. When missing, it by default would use the public endpoint at https://api.github.com. +If you want to upload to a GitHub enterprise instance, then a respective `` connection string must be specified. diff --git a/pom.xml b/pom.xml index 7388e04..dfb6bda 100644 --- a/pom.xml +++ b/pom.xml @@ -48,7 +48,7 @@ UTF-8 - 1.7 + 1.8 ${java.version} ${java.version} @@ -201,7 +201,7 @@ org.kohsuke github-api - 1.95 + 1.317 @@ -222,11 +222,16 @@ 3.5.4 provided + + org.codehaus.plexus + plexus-utils + 3.2.1 + - junit - junit - 4.12 + org.junit.jupiter + junit-jupiter + 5.10.1 test diff --git a/src/main/java/de/jutzig/github/release/plugin/UploadMojo.java b/src/main/java/de/jutzig/github/release/plugin/UploadMojo.java index af42ae7..6ecf54f 100644 --- a/src/main/java/de/jutzig/github/release/plugin/UploadMojo.java +++ b/src/main/java/de/jutzig/github/release/plugin/UploadMojo.java @@ -25,11 +25,14 @@ import org.apache.commons.lang3.StringUtils; import org.apache.maven.model.FileSet; +import org.apache.maven.model.Scm; import org.apache.maven.plugin.AbstractMojo; import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; import org.apache.maven.plugins.annotations.LifecyclePhase; import org.apache.maven.plugins.annotations.Mojo; import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.project.MavenProject; import org.apache.maven.settings.Server; import org.apache.maven.settings.Settings; import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest; @@ -48,6 +51,7 @@ import org.kohsuke.github.GHReleaseBuilder; import org.kohsuke.github.GHRepository; import org.kohsuke.github.GitHub; +import org.kohsuke.github.GitHubBuilder; import org.kohsuke.github.PagedIterable; /** @@ -56,6 +60,7 @@ @Mojo(name = "release", defaultPhase = LifecyclePhase.DEPLOY) public class UploadMojo extends AbstractMojo implements Contextualizable{ + private static final String PUBLIC_GITUHB_API_ENDPOINT = "https://api.github.com"; /** * Server id for github access. */ @@ -149,6 +154,9 @@ public class UploadMojo extends AbstractMojo implements Contextualizable{ @Parameter(defaultValue = "false") private Boolean failOnExistingRelease; + @Component + private MavenProject project; + public void execute() throws MojoExecutionException { if(releaseName==null) releaseName = tag; @@ -270,7 +278,7 @@ private GHRelease findRelease(GHRepository repository, String releaseNameToFind) */ private static final Pattern REPOSITORY_PATTERN = Pattern.compile( "^(scm:git[:|])?" + //Maven prefix for git SCM - "(https?://github\\.com/|git@github\\.com:)" + //GitHub prefix for HTTP/HTTPS/SSH/Subversion scheme + "(https?://[\\w\\d.-]+/|git@[\\w\\d.-]+:)" + //GitHub prefix for HTTP/HTTPS/SSH/Subversion scheme "([^/]+/[^/\\.]+)" + //Repository ID "(\\.git)?" + //Optional suffix ".git" "(/.*)?$" //Optional child project path @@ -285,13 +293,41 @@ public static String computeRepositoryId(String id) { } } + static String computeGithubApiEndpoint(Scm scm) { + if (scm == null || StringUtils.isEmpty(scm.getConnection())) { + return PUBLIC_GITUHB_API_ENDPOINT; + } + Matcher matcher = REPOSITORY_PATTERN.matcher(scm.getConnection()); + if (!matcher.matches()) { + return PUBLIC_GITUHB_API_ENDPOINT; + } + String githubApiEndpoint = matcher.group(2); + if (githubApiEndpoint.contains("github.com")) { + return PUBLIC_GITUHB_API_ENDPOINT; + } + + if (githubApiEndpoint.startsWith("git@")) { + // According to the regex pattern above, the matched group would be in a form of git@hostname: + githubApiEndpoint = githubApiEndpoint.substring(4, githubApiEndpoint.length() - 1); + } + + githubApiEndpoint = StringUtils.removeEnd(githubApiEndpoint, "/"); + if (!githubApiEndpoint.startsWith("http")) { + githubApiEndpoint = "https://" + githubApiEndpoint; + } + // See https://docs.github.com/en/enterprise-server@3.10/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#schema + return githubApiEndpoint + "/api/v3"; + } + public GitHub createGithub(String serverId) throws MojoExecutionException, IOException { String usernameProperty = System.getProperty("username"); String passwordProperty = System.getProperty("password"); + String githubApiEndpoint = computeGithubApiEndpoint(project.getScm()); + GitHubBuilder gitHubBuilder = new GitHubBuilder().withEndpoint(githubApiEndpoint); if(usernameProperty!=null && passwordProperty!=null) { - getLog().debug("Using server credentials from system properties 'username' and 'password'"); - return GitHub.connectUsingPassword(usernameProperty, passwordProperty); + getLog().debug("Using server credentials from system properties 'username' and 'password'"); + return gitHubBuilder.withPassword(usernameProperty, passwordProperty).build(); } Server server = getServer(settings, serverId); @@ -312,9 +348,9 @@ public GitHub createGithub(String serverId) throws MojoExecutionException, IOExc String serverPassword = server.getPassword(); String serverAccessToken = server.getPrivateKey(); if (StringUtils.isNotEmpty(serverUsername) && StringUtils.isNotEmpty(serverPassword)) - return GitHub.connectUsingPassword(serverUsername, serverPassword); + return gitHubBuilder.withPassword(serverUsername, serverPassword).build(); else if (StringUtils.isNotEmpty(serverAccessToken)) - return GitHub.connectUsingOAuth(serverAccessToken); + return gitHubBuilder.withOAuthToken(serverAccessToken).build(); else throw new MojoExecutionException("Configuration for server " + serverId + " has no login credentials"); } diff --git a/src/test/java/de/jutzig/github/release/plugin/UploadMojoTest.java b/src/test/java/de/jutzig/github/release/plugin/UploadMojoTest.java index a1fdc39..305bf37 100644 --- a/src/test/java/de/jutzig/github/release/plugin/UploadMojoTest.java +++ b/src/test/java/de/jutzig/github/release/plugin/UploadMojoTest.java @@ -1,54 +1,71 @@ package de.jutzig.github.release.plugin; -import org.junit.Before; -import org.junit.Test; +import java.util.stream.Stream; -import java.util.HashMap; -import java.util.Map; +import org.apache.maven.model.Scm; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.*; -public class UploadMojoTest { +class UploadMojoTest { + @ParameterizedTest(name = "{0} should resolve to {1} repository id") + @CsvSource({ + // Public + "scm:git:https://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "scm:git|https://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "https://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", - private Map computeRepositoryIdData; + "scm:git:http://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "scm:git|http://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "http://github.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", - @Before - public void setUp() throws Exception { - computeRepositoryIdData = new HashMap(); + "scm:git:git@github.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "scm:git|git@github.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "git@github.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin", - computeRepositoryIdData.put("scm:git:https://github.com/jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("scm:git|https://github.com/jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("https://github.com/jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); + "scm:git:https://github.com/jutzig/github-release-plugin, jutzig/github-release-plugin", + "scm:git|https://github.com/jutzig/github-release-plugin, jutzig/github-release-plugin", + "https://github.com/jutzig/github-release-plugin, jutzig/github-release-plugin", - computeRepositoryIdData.put("scm:git:http://github.com/jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("scm:git|http://github.com/jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("http://github.com/jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); + "scm:git:http://github.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin", + "scm:git|http://github.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin", + "http://github.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin", + + // Enterprise + "scm:git:https://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "scm:git|https://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "https://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", - computeRepositoryIdData.put("scm:git:git@github.com:jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("scm:git|git@github.com:jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("git@github.com:jutzig/github-release-plugin.git", "jutzig/github-release-plugin"); + "scm:git:http://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "scm:git|http://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "http://github.acme.com/jutzig/github-release-plugin.git, jutzig/github-release-plugin", - computeRepositoryIdData.put("scm:git:https://github.com/jutzig/github-release-plugin", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("scm:git|https://github.com/jutzig/github-release-plugin", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("https://github.com/jutzig/github-release-plugin", "jutzig/github-release-plugin"); + "scm:git:git@github.acme.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "scm:git|git@github.acme.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin", + "git@github.acme.com:jutzig/github-release-plugin.git, jutzig/github-release-plugin", - computeRepositoryIdData.put("scm:git:http://github.com/jutzig/github-release-plugin.git/child", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("scm:git|http://github.com/jutzig/github-release-plugin.git/child", "jutzig/github-release-plugin"); - computeRepositoryIdData.put("http://github.com/jutzig/github-release-plugin.git/child", "jutzig/github-release-plugin"); + "scm:git:https://github.acme.com/jutzig/github-release-plugin, jutzig/github-release-plugin", + "scm:git|https://github.acme.com/jutzig/github-release-plugin, jutzig/github-release-plugin", + "https://github.acme.com/jutzig/github-release-plugin, jutzig/github-release-plugin", + + "scm:git:http://github.acme.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin", + "scm:git|http://github.acme.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin", + "http://github.acme.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin" + }) + void testComputeRepositoryId(String scmString, String expectedRepositoryId) { + assertEquals(expectedRepositoryId, UploadMojo.computeRepositoryId(scmString)); } - @Test - public void testComputeRepositoryId() throws Exception { - for (String source : computeRepositoryIdData.keySet()) { - String expected = computeRepositoryIdData.get(source); - assertEquals(source, expected, UploadMojo.computeRepositoryId(source)); - } + @ParameterizedTest(name = "{0} should resolve to {1} endpoint") + @MethodSource("scmFixture") + void testGithubEndpoint(Scm scm, String expectedEndpoint) { + assertEquals(expectedEndpoint, UploadMojo.computeGithubApiEndpoint(scm)); } @Test - public void testGuessPreRelease() { + void testGuessPreRelease() { assertTrue(UploadMojo.guessPreRelease("1.0-SNAPSHOT")); assertTrue(UploadMojo.guessPreRelease("1.0-alpha")); assertTrue(UploadMojo.guessPreRelease("1.0-alpha-1")); @@ -62,4 +79,59 @@ public void testGuessPreRelease() { assertFalse(UploadMojo.guessPreRelease("1")); assertFalse(UploadMojo.guessPreRelease("1.0")); } + + private static Stream scmFixture() { + return Stream.of( + // Public GitHub + Arguments.of(scmWithConnectionString("scm:git:https://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("scm:git|https://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("https://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"), + + Arguments.of(scmWithConnectionString("scm:git:http://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("scm:git|http://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("http://github.com/jutzig/github-release-plugin.git"), "https://api.github.com"), + + Arguments.of(scmWithConnectionString("scm:git:git@github.com:jutzig/github-release-plugin.git"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("scm:git|git@github.com:jutzig/github-release-plugin.git"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("git@github.com:jutzig/github-release-plugin.git"), "https://api.github.com"), + + Arguments.of(scmWithConnectionString("scm:git:https://github.com/jutzig/github-release-plugin"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("scm:git|https://github.com/jutzig/github-release-plugin"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("https://github.com/jutzig/github-release-plugin"), "https://api.github.com"), + + Arguments.of(scmWithConnectionString("scm:git:http://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("scm:git|http://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"), + Arguments.of(scmWithConnectionString("http://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"), + + // GitHub Enterprise + Arguments.of(scmWithConnectionString("scm:git:https://github.acme.com/jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("scm:git|https://github.acme.com/jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("https://github.acme.com/jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"), + + Arguments.of(scmWithConnectionString("scm:git:http://github.acme.com/jutzig/github-release-plugin.git"), "http://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("scm:git|http://github.acme.com/jutzig/github-release-plugin.git"), "http://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("http://github.acme.com/jutzig/github-release-plugin.git"), "http://github.acme.com/api/v3"), + + Arguments.of(scmWithConnectionString("scm:git:git@github.acme.com:jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("scm:git|git@github.acme.com:jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("git@github.acme.com:jutzig/github-release-plugin.git"), "https://github.acme.com/api/v3"), + + Arguments.of(scmWithConnectionString("scm:git:https://github.acme.com/jutzig/github-release-plugin"), "https://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("scm:git|https://github.acme.com/jutzig/github-release-plugin"), "https://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("https://github.acme.com/jutzig/github-release-plugin"), "https://github.acme.com/api/v3"), + + Arguments.of(scmWithConnectionString("scm:git:http://github.acme.com/jutzig/github-release-plugin.git/child"), "http://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("scm:git|http://github.acme.com/jutzig/github-release-plugin.git/child"), "http://github.acme.com/api/v3"), + Arguments.of(scmWithConnectionString("http://github.acme.com/jutzig/github-release-plugin.git/child"), "http://github.acme.com/api/v3"), + + // Fallback to public + Arguments.of(null, "https://api.github.com") + ); + } + + private static Scm scmWithConnectionString(String connection) { + Scm scm = new Scm(); + scm.setConnection(connection); + return scm; + } }