Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<scm>` 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 `<scm>` connection string must be specified.
15 changes: 10 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.7</java.version>
<java.version>1.8</java.version>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.7 is officially EOL by Oracle since 2022 and EOL by OpenJDK since 2020.

Also maven-plugin-api from 3.1.0 onward requires JDK 8

Besides that, it hinders the usage of JUnit 5 which makes writing parameterized tests so much easier.

<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
Expand Down Expand Up @@ -201,7 +201,7 @@
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>1.95</version>
<version>1.317</version>
Copy link
Copy Markdown
Contributor Author

@anenviousguest anenviousguest Nov 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.95 was using the hard-coded string URL for uploading the artifacts (upload.github.com), despite of the endpoint customization via GitHub builder. So I simply upgraded to the most recent version where URL customization has been fixed.

</dependency>

<dependency>
Expand All @@ -222,11 +222,16 @@
<version>3.5.4</version>
<scope>provided</scope>
</dependency>
<dependency>
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From Maven 3.9.0 and onwards, plexus-utils is no longer placed to plugin classpath by default (see release notes), so I had to add this one to ensure the code can run also when using recent Maven versions, which is the case in our environment.

3.2.1 is the one which works with maven-plugins-api of version 3.6.2 used by this plugin. See dependency page.

<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
<version>3.2.1</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.1</version>
<scope>test</scope>
</dependency>
</dependencies>
Expand Down
46 changes: 41 additions & 5 deletions src/main/java/de/jutzig/github/release/plugin/UploadMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

/**
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to allow other hostnames. Alphanumeric, dots, dashes. Not perfect but better than nothing.

"([^/]+/[^/\\.]+)" + //Repository ID
"(\\.git)?" + //Optional suffix ".git"
"(/.*)?$" //Optional child project path
Expand All @@ -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);
Expand All @@ -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");
}
Expand Down
140 changes: 106 additions & 34 deletions src/test/java/de/jutzig/github/release/plugin/UploadMojoTest.java
Original file line number Diff line number Diff line change
@@ -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<String, String> computeRepositoryIdData;
"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",

@Before
public void setUp() throws Exception {
computeRepositoryIdData = new HashMap<String, String>();
"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: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.git/child, jutzig/github-release-plugin",
"scm:git|https://github.com/jutzig/github-release-plugin.git/child, jutzig/github-release-plugin",
"https://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:https://github.com/jutzig/github-release-plugin.git/child", "jutzig/github-release-plugin");
computeRepositoryIdData.put("scm:git|https://github.com/jutzig/github-release-plugin.git/child", "jutzig/github-release-plugin");
computeRepositoryIdData.put("https://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"));
Expand All @@ -62,4 +79,59 @@ public void testGuessPreRelease() {
assertFalse(UploadMojo.guessPreRelease("1"));
assertFalse(UploadMojo.guessPreRelease("1.0"));
}

private static Stream<Arguments> 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: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: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:https://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"),
Arguments.of(scmWithConnectionString("scm:git|https://github.com/jutzig/github-release-plugin.git/child"), "https://api.github.com"),
Arguments.of(scmWithConnectionString("https://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;
}
}