diff --git a/.gitignore b/.gitignore
index 9515391b..2989fb2d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,14 +3,13 @@ target/
!**/src/main/**/target/
!**/src/test/**/target/
-### Plugins Directory ###
-test-plugins
-
### Log file ###
logs/
### IntelliJ IDEA ###
.idea/
+!.idea/runConfigurations/
+!.idea/.gitignore/
*.iws
*.iml
*.ipr
@@ -34,6 +33,8 @@ build/
!**/src/main/**/build/
!**/src/test/**/build/
+!**/test/resources/**/.git
+
### VS Code ###
.vscode/
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..26d33521
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/Jenkinsfile b/Jenkinsfile
index 438a9b51..0af57689 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -1,8 +1,11 @@
+@Library('pipeline-library@pull/893/head') _
+
// While this isn't a plugin, it is much simpler to reuse the pipeline code for CI
// allowing easy windows / linux testing and producing incrementals
// the only feature that buildPlugin has that relates to plugins is allowing you to test against multiple jenkins versions
buildPlugin(
useContainerAgent: false,
+ useArtifactCachingProxy: false,
configurations: [
[platform: 'linux', jdk: 21],
[platform: 'windows', jdk: 21],
diff --git a/README.md b/README.md
index 86c4038b..9e99445a 100644
--- a/README.md
+++ b/README.md
@@ -104,13 +104,13 @@ From there you need to save both ID of installation (found on URL)
## Global option
-- `--debug` or `-d`: (optional) Enables debug mode. Defaults to false.
+- `--debug`: (optional) Enables debug mode. Defaults to false.
-- `--cache-path` or `-c`: (optional) Custom path to the cache directory. Defaults to `${user.home}/.cache/jenkins-plugin-modernizer-cli`.
+- `--cache-path`: (optional) Custom path to the cache directory. Defaults to `${user.home}/.cache/jenkins-plugin-modernizer-cli`.
-- `--maven-home` or `-m`: (optional) Path to the Maven home directory. Required if both `MAVEN_HOME` and `M2_HOME` environment variables are not set. The minimum required version is 3.9.7.
+- `--maven-home`: (optional) Path to the Maven home directory. Required if both `MAVEN_HOME` and `M2_HOME` environment variables are not set. The minimum required version is 3.9.7.
- `--clean-local-data` (optional) Deletes the local plugin directory before running the tool.
diff --git a/plugin-modernizer-cli/pom.xml b/plugin-modernizer-cli/pom.xml
index 3008e95c..96f2ad77 100644
--- a/plugin-modernizer-cli/pom.xml
+++ b/plugin-modernizer-cli/pom.xml
@@ -122,6 +122,7 @@
**/*ITCase.java
+ ${maven.repo.local}
${project.build.directory}/apache-maven-${maven.version}
fake-token
fake-owner
diff --git a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/BuildMetadataCommand.java b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/BuildMetadataCommand.java
index a50034e2..adb66acf 100644
--- a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/BuildMetadataCommand.java
+++ b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/BuildMetadataCommand.java
@@ -6,6 +6,8 @@
import io.jenkins.tools.pluginmodernizer.core.config.Config;
import io.jenkins.tools.pluginmodernizer.core.config.Settings;
import io.jenkins.tools.pluginmodernizer.core.impl.PluginModernizer;
+import io.jenkins.tools.pluginmodernizer.core.model.ModernizerException;
+import java.nio.file.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
@@ -27,6 +29,14 @@ public class BuildMetadataCommand implements ICommand {
@CommandLine.ArgGroup(exclusive = true, multiplicity = "1")
private PluginOptions pluginOptions;
+ /**
+ * Path to the authentication key in case of private repo
+ */
+ @CommandLine.Option(
+ names = {"--ssh-private-key"},
+ description = "Path to the authentication key for GitHub. Default to ~/.ssh/id_rsa")
+ private Path sshPrivateKey = Settings.SSH_PRIVATE_KEY;
+
/**
* Global options for all commands
*/
@@ -44,12 +54,21 @@ public Config setup(Config.Builder builder) {
options.config(builder);
envOptions.config(builder);
pluginOptions.config(builder);
- return builder.withRecipe(Settings.FETCH_METADATA_RECIPE).build();
+ return builder.withSshPrivateKey(sshPrivateKey)
+ .withRecipe(Settings.FETCH_METADATA_RECIPE)
+ .build();
}
@Override
- public Integer call() throws Exception {
+ public Integer call() {
PluginModernizer modernizer = getModernizer();
+ try {
+ modernizer.validate();
+ } catch (ModernizerException e) {
+ LOG.error("Validation error");
+ LOG.error(e.getMessage());
+ return 1;
+ }
modernizer.start();
return 0;
}
diff --git a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/ValidateCommand.java b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/ValidateCommand.java
index 4f8ab5f9..97473f7e 100644
--- a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/ValidateCommand.java
+++ b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/command/ValidateCommand.java
@@ -6,6 +6,8 @@
import io.jenkins.tools.pluginmodernizer.core.config.Config;
import io.jenkins.tools.pluginmodernizer.core.impl.PluginModernizer;
import io.jenkins.tools.pluginmodernizer.core.model.ModernizerException;
+import java.nio.file.Files;
+import java.nio.file.Path;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;
@@ -53,7 +55,13 @@ public Integer call() throws Exception {
try {
modernizer.validate();
LOG.info("GitHub owner: {}", modernizer.getGithubOwner());
+ if (Files.isRegularFile(Path.of(modernizer.getSshPrivateKeyPath()))) {
+ LOG.info("SSH key path: {}", modernizer.getSshPrivateKeyPath());
+ } else {
+ LOG.info("SSH key not set. Will use GitHub token for Git operation");
+ }
LOG.info("Maven home: {}", modernizer.getMavenHome());
+ LOG.info("Maven local repository: {}", modernizer.getMavenLocalRepo());
LOG.info("Maven version: {}", modernizer.getMavenVersion());
LOG.info("Java version: {}", modernizer.getJavaVersion());
LOG.info("Cache path: {}", modernizer.getCachePath());
diff --git a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GitHubOptions.java b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GitHubOptions.java
index 73d9c695..0eda3765 100644
--- a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GitHubOptions.java
+++ b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GitHubOptions.java
@@ -2,6 +2,7 @@
import io.jenkins.tools.pluginmodernizer.core.config.Config;
import io.jenkins.tools.pluginmodernizer.core.config.Settings;
+import java.nio.file.Path;
import picocli.CommandLine;
/**
@@ -15,6 +16,14 @@
commandListHeading = "%nCommands:%n")
public class GitHubOptions implements IOption {
+ /**
+ * Path to the authentication key
+ */
+ @CommandLine.Option(
+ names = {"--ssh-private-key"},
+ description = "Path to the authentication key for GitHub. Default to ~/.ssh/id_rsa")
+ private Path sshPrivateKey = Settings.SSH_PRIVATE_KEY;
+
@CommandLine.Option(
names = {"-g", "--github-owner"},
description = "GitHub owner for forked repositories.")
@@ -46,6 +55,7 @@ public void config(Config.Builder builder) {
builder.withGitHubOwner(githubOwner)
.withGitHubAppId(githubAppId)
.withGitHubAppSourceInstallationId(githubAppSourceInstallationId)
- .withGitHubAppTargetInstallationId(githubAppTargetInstallationId);
+ .withGitHubAppTargetInstallationId(githubAppTargetInstallationId)
+ .withSshPrivateKey(sshPrivateKey);
}
}
diff --git a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GlobalOptions.java b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GlobalOptions.java
index 9bbd27b4..4b76eaf9 100644
--- a/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GlobalOptions.java
+++ b/plugin-modernizer-cli/src/main/java/io/jenkins/tools/pluginmodernizer/cli/options/GlobalOptions.java
@@ -20,20 +20,25 @@
public class GlobalOptions implements IOption {
@CommandLine.Option(
- names = {"-d", "--debug"},
+ names = {"--debug"},
description = "Enable debug logging.")
public boolean debug;
@CommandLine.Option(
- names = {"-c", "--cache-path"},
+ names = {"--cache-path"},
description = "Path to the cache directory.")
public Path cachePath = Settings.DEFAULT_CACHE_PATH;
@CommandLine.Option(
- names = {"-m", "--maven-home"},
+ names = {"--maven-home"},
description = "Path to the Maven Home directory.")
public Path mavenHome = Settings.DEFAULT_MAVEN_HOME;
+ @CommandLine.Option(
+ names = {"--maven-local-repo"},
+ description = "Path to the Maven local repository.")
+ public Path mavenLocalRepo = Settings.DEFAULT_MAVEN_LOCAL_REPO;
+
/**
* Create a new config build for the global options
*/
@@ -45,7 +50,8 @@ public void config(Config.Builder builder) {
!cachePath.endsWith(Settings.CACHE_SUBDIR)
? cachePath.resolve(Settings.CACHE_SUBDIR)
: cachePath)
- .withMavenHome(mavenHome);
+ .withMavenHome(mavenHome)
+ .withMavenLocalRepo(mavenLocalRepo);
}
/**
diff --git a/plugin-modernizer-cli/src/main/resources/logback.xml b/plugin-modernizer-cli/src/main/resources/logback.xml
index 5cfcf798..44885f6f 100644
--- a/plugin-modernizer-cli/src/main/resources/logback.xml
+++ b/plugin-modernizer-cli/src/main/resources/logback.xml
@@ -43,8 +43,12 @@
-
-
+
+
+
+
+
+
diff --git a/plugin-modernizer-cli/src/test/java/io/jenkins/tools/pluginmodernizer/cli/CommandLineITCase.java b/plugin-modernizer-cli/src/test/java/io/jenkins/tools/pluginmodernizer/cli/CommandLineITCase.java
index 8e9d982f..050fa254 100644
--- a/plugin-modernizer-cli/src/test/java/io/jenkins/tools/pluginmodernizer/cli/CommandLineITCase.java
+++ b/plugin-modernizer-cli/src/test/java/io/jenkins/tools/pluginmodernizer/cli/CommandLineITCase.java
@@ -5,31 +5,40 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
-import com.github.sparsick.testcontainers.gitserver.GitServerVersions;
-import com.github.sparsick.testcontainers.gitserver.http.GitHttpServerContainer;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
-import io.jenkins.tools.pluginmodernizer.core.model.HealthScoreData;
-import io.jenkins.tools.pluginmodernizer.core.model.PluginVersionData;
-import io.jenkins.tools.pluginmodernizer.core.model.UpdateCenterData;
+import io.jenkins.tools.pluginmodernizer.cli.utils.GitHubServerContainer;
+import io.jenkins.tools.pluginmodernizer.core.extractor.PluginMetadata;
+import io.jenkins.tools.pluginmodernizer.core.impl.CacheManager;
+import io.jenkins.tools.pluginmodernizer.core.model.Plugin;
+import io.jenkins.tools.pluginmodernizer.core.utils.JsonUtils;
import java.io.File;
+import java.io.FileWriter;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
-import java.util.Map;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.Security;
import java.util.Properties;
+import java.util.stream.Stream;
import org.apache.maven.shared.invoker.DefaultInvocationRequest;
import org.apache.maven.shared.invoker.DefaultInvoker;
import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationResult;
import org.apache.maven.shared.invoker.Invoker;
+import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.shaded.org.bouncycastle.jce.provider.BouncyCastleProvider;
/**
* Integration test for the command line interface
@@ -38,20 +47,37 @@
@Testcontainers(disabledWithoutDocker = true)
public class CommandLineITCase {
- @Container
- private GitHttpServerContainer gitRemote = new GitHttpServerContainer(GitServerVersions.V2_45.getDockerImageName());
+ static {
+ Security.addProvider(new BouncyCastleProvider());
+ }
/**
* Logger
*/
private static final Logger LOG = LoggerFactory.getLogger(CommandLineITCase.class);
+ /**
+ * Tests plugins
+ * @return the plugins
+ */
+ private static Stream testsPlugins() {
+ return Stream.of(Arguments.of(new PluginMetadata() {
+ {
+ setPluginName("empty");
+ setJenkinsVersion("2.440.3");
+ }
+ }));
+ }
+
@TempDir
private Path outputPath;
@TempDir
private Path cachePath;
+ @TempDir
+ private Path keysPath;
+
@Test
public void testVersion() throws Exception {
LOG.info("Running testVersion");
@@ -100,6 +126,32 @@ public void testCleanup() throws Exception {
.anyMatch(line -> line.matches("(.*)Removed path: (.*)"))));
}
+ @Test
+ public void testValidateWithSshKey(WireMockRuntimeInfo wmRuntimeInfo) throws Exception {
+ LOG.info("Running testValidateWithSshKey");
+
+ // Setup
+ WireMock wireMock = wmRuntimeInfo.getWireMock();
+ wireMock.register(WireMock.get(WireMock.urlEqualTo("/api/user"))
+ .willReturn(
+ WireMock.jsonResponse(new GitHubServerContainer.UserApiResponse("fake-owner", "User"), 200)));
+
+ Invoker invoker = buildInvoker();
+ InvocationRequest request =
+ buildRequest("validate --maven-home %s --ssh-private-key %s --debug --github-api-url %s/api"
+ .formatted(
+ getModernizerMavenHome(),
+ generatePrivateKey("testValidateWithSshKey"),
+ wmRuntimeInfo.getHttpBaseUrl()));
+ InvocationResult result = invoker.execute(request);
+ assertAll(
+ () -> assertEquals(0, result.getExitCode()),
+ () -> assertTrue(Files.readAllLines(outputPath.resolve("stdout.txt")).stream()
+ .anyMatch(line -> line.matches("(.*)GitHub owner: fake-owner(.*)"))),
+ () -> assertTrue(Files.readAllLines(outputPath.resolve("stdout.txt")).stream()
+ .anyMatch(line -> line.matches("(.*)Validation successful(.*)"))));
+ }
+
@Test
public void testValidate(WireMockRuntimeInfo wmRuntimeInfo) throws Exception {
LOG.info("Running testValidate");
@@ -107,11 +159,12 @@ public void testValidate(WireMockRuntimeInfo wmRuntimeInfo) throws Exception {
// Setup
WireMock wireMock = wmRuntimeInfo.getWireMock();
wireMock.register(WireMock.get(WireMock.urlEqualTo("/api/user"))
- .willReturn(WireMock.jsonResponse(USER_API_RESPONSE, 200)));
+ .willReturn(
+ WireMock.jsonResponse(new GitHubServerContainer.UserApiResponse("fake-owner", "User"), 200)));
Invoker invoker = buildInvoker();
InvocationRequest request =
- buildRequest("validate --debug --github-api-url " + wmRuntimeInfo.getHttpBaseUrl() + "/api");
+ buildRequest("validate --debug --github-api-url %s/api".formatted(wmRuntimeInfo.getHttpBaseUrl()));
InvocationResult result = invoker.execute(request);
assertAll(
() -> assertEquals(0, result.getExitCode()),
@@ -134,60 +187,41 @@ public void testListRecipes() throws Exception {
line -> line.matches(".*FetchMetadata - Extracts metadata from a Jenkins plugin.*"))));
}
- @Test
- public void testBuildMetadata(WireMockRuntimeInfo wmRuntimeInfo) throws Exception {
- LOG.info("Running testBuildMetadataForDeprecatedPlugin");
-
- PluginStatsApiResponse pluginStatsApiResponse = new PluginStatsApiResponse(Map.of("a-fake-plugin", 1));
- UpdateCenterApiResponse updateCenterApiResponse = new UpdateCenterApiResponse(
- Map.of(
- "a-fake-plugin",
- new UpdateCenterData.UpdateCenterPlugin(
- "a-fake-plugin",
- "1",
- gitRemote.getGitRepoURIAsHttp().toString(),
- "main",
- "io.jenkins.plugins:a-fake",
- null)),
- Map.of(
- "a-fake-plugin",
- new UpdateCenterData.DeprecatedPlugin("https://github.com/jenkinsci/a-fake-plugin")));
- PluginVersionsApiResponse pluginVersionsApiResponse = new PluginVersionsApiResponse(
- Map.of("a-fake-plugin", Map.of("1", new PluginVersionData.PluginVersionPlugin("a-fake-plugin", "1"))));
- HealthScoreApiResponse pluginHealthScoreApiResponse =
- new HealthScoreApiResponse(Map.of("a-fake-plugin", new HealthScoreData.HealthScorePlugin(100d)));
+ @ParameterizedTest
+ @MethodSource("testsPlugins")
+ public void testBuildMetadata(PluginMetadata expectedMetadata, WireMockRuntimeInfo wmRuntimeInfo) throws Exception {
- // Setup
- WireMock wireMock = wmRuntimeInfo.getWireMock();
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/api/user"))
- .willReturn(WireMock.jsonResponse(USER_API_RESPONSE, 200)));
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/api/repos/jenkinsci/testRepo"))
- .willReturn(WireMock.jsonResponse(
- new RepoApiResponse(gitRemote.getGitRepoURIAsHttp().toString()), 200)));
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/update-center.json"))
- .willReturn(WireMock.jsonResponse(updateCenterApiResponse, 200)));
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/plugin-versions.json"))
- .willReturn(WireMock.jsonResponse(pluginVersionsApiResponse, 200)));
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/scores"))
- .willReturn(WireMock.jsonResponse(pluginHealthScoreApiResponse, 200)));
- wireMock.register(WireMock.get(WireMock.urlEqualTo("/jenkins-stats/svg/202406-plugins.csv"))
- .willReturn(WireMock.jsonResponse(pluginStatsApiResponse, 200)));
+ String plugin = expectedMetadata.getPluginName();
- Invoker invoker = buildInvoker();
- InvocationRequest request = buildRequest(
- "build-metadata --plugins a-fake-plugin --debug --cache-path %s --github-api-url %s --jenkins-update-center %s --jenkins-plugin-info %s --plugin-health-score %s --jenkins-plugins-stats-installations-url %s"
- .formatted(
- cachePath,
- wmRuntimeInfo.getHttpBaseUrl() + "/api",
- wmRuntimeInfo.getHttpBaseUrl() + "/update-center.json",
- wmRuntimeInfo.getHttpBaseUrl() + "/plugin-versions.json",
- wmRuntimeInfo.getHttpBaseUrl() + "/scores",
- wmRuntimeInfo.getHttpBaseUrl() + "/jenkins-stats/svg/202406-plugins.csv"));
- InvocationResult result = invoker.execute(request);
- assertAll(
- () -> assertEquals(0, result.getExitCode()),
- () -> assertTrue(Files.readAllLines(outputPath.resolve("stdout.txt")).stream()
- .anyMatch(line -> line.matches(".*Error: Plugin is deprecated.*"))));
+ // Junit attachment with logs file for the plugin build
+ System.out.printf(
+ "[[ATTACHMENT|%s]]%n", Plugin.build(plugin).getLogFile().toAbsolutePath());
+
+ try (GitHubServerContainer gitRemote = new GitHubServerContainer(wmRuntimeInfo, keysPath, plugin, "main")) {
+
+ gitRemote.start();
+
+ Invoker invoker = buildInvoker();
+ InvocationRequest request = buildRequest("build-metadata %s".formatted(getRunArgs(wmRuntimeInfo, plugin)));
+ InvocationResult result = invoker.execute(request);
+
+ // Assert output
+ assertAll(
+ () -> assertEquals(0, result.getExitCode()),
+ () -> assertTrue(Files.readAllLines(outputPath.resolve("stdout.txt")).stream()
+ .anyMatch(line ->
+ line.matches(".*Metadata was fetched for plugin empty and is available at.*"))));
+
+ // Assert some metadata
+ PluginMetadata metadata = JsonUtils.fromJson(
+ cachePath
+ .resolve("jenkins-plugin-modernizer-cli")
+ .resolve(plugin)
+ .resolve(CacheManager.PLUGIN_METADATA_CACHE_KEY),
+ PluginMetadata.class);
+
+ assertEquals(expectedMetadata.getJenkinsVersion(), metadata.getJenkinsVersion());
+ }
}
/**
@@ -262,21 +296,59 @@ private InvocationRequest buildRequest(String args) {
}
/**
- * Login API response
+ * Generate a Ed25519 private key and save it
+ * @param name The name of the key
+ * @throws Exception If an error occurs
*/
- private record UserApiResponse(String login, String type) {}
-
- private record RepoApiResponse(String clone_url) {}
-
- private static final UserApiResponse USER_API_RESPONSE = new UserApiResponse("fake-owner", "User");
-
- private record PluginStatsApiResponse(Map plugins) {}
-
- private record UpdateCenterApiResponse(
- Map plugins,
- Map deprecations) {}
+ private Path generatePrivateKey(String name) throws Exception {
+ KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Ed25519", "BC");
+ KeyPair pair = keyPairGenerator.generateKeyPair();
+ PrivateKey privateKey = pair.getPrivate();
+ File privateKeyFile = keysPath.resolve(name).toFile();
+ try (FileWriter fileWriter = new FileWriter(privateKeyFile);
+ JcaPEMWriter pemWriter = new JcaPEMWriter(fileWriter)) {
+ pemWriter.writeObject(privateKey);
+ }
+ return privateKeyFile.toPath();
+ }
- private record PluginVersionsApiResponse(Map> plugins) {}
+ /**
+ * Get the modernizer maven home
+ * @return Use version from the target directory
+ */
+ private Path getModernizerMavenHome() {
+ return Path.of("target/apache-maven-3.9.9").toAbsolutePath();
+ }
- private record HealthScoreApiResponse(Map plugins) {}
+ /**
+ * Get the URL arguments
+ * @param wmRuntimeInfo The WireMock runtime info
+ * @param plugin The plugin
+ * @return the URL arguments
+ */
+ private String getRunArgs(WireMockRuntimeInfo wmRuntimeInfo, String plugin) {
+ return """
+ --plugins %s
+ --debug
+ --maven-home %s
+ --ssh-private-key %s
+ --cache-path %s
+ --github-api-url %s
+ --jenkins-update-center %s
+ --jenkins-plugin-info %s
+ --plugin-health-score %s
+ --jenkins-plugins-stats-installations-url %s
+ """
+ .formatted(
+ plugin,
+ getModernizerMavenHome(),
+ keysPath.resolve(plugin),
+ cachePath,
+ wmRuntimeInfo.getHttpBaseUrl() + "/api",
+ wmRuntimeInfo.getHttpBaseUrl() + "/update-center.json",
+ wmRuntimeInfo.getHttpBaseUrl() + "/plugin-versions.json",
+ wmRuntimeInfo.getHttpBaseUrl() + "/scores",
+ wmRuntimeInfo.getHttpBaseUrl() + "/jenkins-stats/svg/202406-plugins.csv")
+ .replaceAll("\\s+", " ");
+ }
}
diff --git a/plugin-modernizer-cli/src/test/java/io/jenkins/tools/pluginmodernizer/cli/utils/GitHubServerContainer.java b/plugin-modernizer-cli/src/test/java/io/jenkins/tools/pluginmodernizer/cli/utils/GitHubServerContainer.java
new file mode 100644
index 00000000..b32aff6a
--- /dev/null
+++ b/plugin-modernizer-cli/src/test/java/io/jenkins/tools/pluginmodernizer/cli/utils/GitHubServerContainer.java
@@ -0,0 +1,221 @@
+package io.jenkins.tools.pluginmodernizer.cli.utils;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import com.github.sparsick.testcontainers.gitserver.GitServerVersions;
+import com.github.sparsick.testcontainers.gitserver.plain.GitServerContainer;
+import com.github.sparsick.testcontainers.gitserver.plain.SshIdentity;
+import com.github.tomakehurst.wiremock.client.WireMock;
+import com.github.tomakehurst.wiremock.junit5.WireMockRuntimeInfo;
+import io.jenkins.tools.pluginmodernizer.core.model.HealthScoreData;
+import io.jenkins.tools.pluginmodernizer.core.model.ModernizerException;
+import io.jenkins.tools.pluginmodernizer.core.model.PluginVersionData;
+import io.jenkins.tools.pluginmodernizer.core.model.UpdateCenterData;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.Container;
+import org.testcontainers.containers.ExecConfig;
+import org.testcontainers.utility.MountableFile;
+
+/**
+ * The GitHub server container accessible by SSH
+ */
+public class GitHubServerContainer extends GitServerContainer {
+
+ /**
+ * The logger
+ */
+ private static final Logger LOG = LoggerFactory.getLogger(GitHubServerContainer.class);
+
+ /**
+ * The plugin name
+ */
+ private final String plugin;
+
+ /**
+ * The WireMock runtime info
+ */
+ private final WireMockRuntimeInfo wmRuntimeInfo;
+
+ /**
+ * The branch
+ */
+ private final String branch;
+
+ /**
+ * The path containers the SSH
+ */
+ private final Path keysPath;
+
+ /**
+ * Create a GitHub server container
+ */
+ public GitHubServerContainer(WireMockRuntimeInfo wmRuntimeInfo, Path keysPath, String plugin, String branch) {
+ super(GitServerVersions.V2_45.getDockerImageName());
+ this.plugin = plugin;
+ this.wmRuntimeInfo = wmRuntimeInfo;
+ this.branch = branch;
+ this.keysPath = keysPath;
+ withSshKeyAuth();
+ withGitRepo(plugin);
+ }
+
+ @Override
+ public void start() {
+ super.start();
+ setupGitContainer();
+ setupMock();
+ }
+
+ /**
+ * Setup mocks for integration tests with WireMock and Testcontainer git
+ */
+ private void setupMock() {
+
+ // Setup responses
+ PluginStatsApiResponse pluginStatsApiResponse = new PluginStatsApiResponse(Map.of(plugin, 1));
+ UpdateCenterApiResponse updateCenterApiResponse = new UpdateCenterApiResponse(
+ Map.of(
+ plugin,
+ new UpdateCenterData.UpdateCenterPlugin(
+ plugin,
+ "1",
+ this.getGitRepoURIAsSSH().toString(),
+ branch,
+ "io.jenkins.plugins:%s".formatted(plugin),
+ null)),
+ Map.of());
+ PluginVersionsApiResponse pluginVersionsApiResponse = new PluginVersionsApiResponse(
+ Map.of(plugin, Map.of("1", new PluginVersionData.PluginVersionPlugin(plugin, "1"))));
+ HealthScoreApiResponse pluginHealthScoreApiResponse =
+ new HealthScoreApiResponse(Map.of(plugin, new HealthScoreData.HealthScorePlugin(100d)));
+
+ // Setup mocks
+ WireMock wireMock = wmRuntimeInfo.getWireMock();
+ wireMock.register(WireMock.get(WireMock.urlEqualTo("/api/user"))
+ .willReturn(WireMock.jsonResponse(new UserApiResponse("fake-owner", "User"), 200)));
+ wireMock.register(WireMock.get(WireMock.urlEqualTo("/api/repos/jenkinsci/%s".formatted(plugin)))
+ .willReturn(WireMock.jsonResponse(
+ new RepoApiResponse(
+ "main",
+ "%s/%s/%s".formatted(wmRuntimeInfo.getHttpBaseUrl(), "fake-owner", plugin),
+ this.getGitRepoURIAsSSH().toString()),
+ 200)));
+ wireMock.register(WireMock.get(WireMock.urlEqualTo("/update-center.json"))
+ .willReturn(WireMock.jsonResponse(updateCenterApiResponse, 200)));
+ wireMock.register(WireMock.get(WireMock.urlEqualTo("/plugin-versions.json"))
+ .willReturn(WireMock.jsonResponse(pluginVersionsApiResponse, 200)));
+ wireMock.register(WireMock.get(WireMock.urlEqualTo("/scores"))
+ .willReturn(WireMock.jsonResponse(pluginHealthScoreApiResponse, 200)));
+ wireMock.register(WireMock.get(WireMock.urlEqualTo("/jenkins-stats/svg/202406-plugins.csv"))
+ .willReturn(WireMock.jsonResponse(pluginStatsApiResponse, 200)));
+
+ // Setup SSH key to access the container
+ SshIdentity sshIdentity = this.getSshClientIdentity();
+ byte[] privateKey = sshIdentity.getPrivateKey();
+ try {
+ Files.write(keysPath.resolve(plugin), privateKey);
+ LOG.info("Private key: {}", keysPath.resolve(plugin));
+ } catch (IOException e) {
+ throw new ModernizerException("Error writing private key", e);
+ }
+ }
+
+ /**
+ * Run a command on the git container
+ * @param command The command
+ */
+ private void runContainerGitCommand(String user, String command) {
+ try {
+ Container.ExecResult containerResult = this.execInContainer(ExecConfig.builder()
+ .user(user)
+ .command(command.split(" ")) // We assume no command contains space
+ .build());
+ LOG.debug("Running command on container: {}", command);
+ LOG.debug("Stdout: {}", containerResult.getStdout());
+ LOG.debug("Stderr: {}", containerResult.getStderr());
+
+ assertEquals(
+ 0,
+ containerResult.getExitCode(),
+ "Command '%s' failed with status code '%s' and output '%s' and error '%s'"
+ .formatted(
+ command,
+ containerResult.getExitCode(),
+ containerResult.getStdout(),
+ containerResult.getStderr()));
+ } catch (IOException | InterruptedException e) {
+ throw new ModernizerException("Error running command: %s".formatted(command), e);
+ }
+ }
+
+ /**
+ * Setup the git container
+ */
+ private void setupGitContainer() {
+ String gitRepoPath = String.format("/srv/git/%s.git", plugin);
+ String sourceDirectory = "src/test/resources/%s".formatted(plugin);
+ assertTrue(
+ Files.exists(Path.of(sourceDirectory)),
+ "Source directory %s does not exist".formatted(sourceDirectory));
+ runContainerGitCommand("root", "rm -Rf %s".formatted(gitRepoPath));
+ LOG.debug("Copying %s to %s".formatted(sourceDirectory, gitRepoPath));
+ this.copyFileToContainer(MountableFile.forHostPath(sourceDirectory.formatted(plugin)), gitRepoPath);
+ LOG.debug("Copied %s to %s".formatted(sourceDirectory, gitRepoPath));
+ runContainerGitCommand("root", "chown -R git:git %s".formatted(gitRepoPath));
+ runContainerGitCommand("git", "ls -la %s".formatted(gitRepoPath));
+ runContainerGitCommand("git", "git config --global init.defaultBranch main");
+ runContainerGitCommand("git", "git config --global --add safe.directory %s".formatted(gitRepoPath));
+ runContainerGitCommand("git", "git config --global user.name Fake");
+ runContainerGitCommand("git", "git config --global user.email fake-email@example.com");
+ runContainerGitCommand("git", "git init %s".formatted(gitRepoPath));
+ runContainerGitCommand("git", "git -C %s status".formatted(gitRepoPath));
+ runContainerGitCommand("git", "git -C %s add .".formatted(gitRepoPath));
+ runContainerGitCommand("git", "git -C %s status".formatted(gitRepoPath));
+ runContainerGitCommand("git", "git -C %s commit -m init".formatted(gitRepoPath));
+ runContainerGitCommand("git", "git -C %s status".formatted(gitRepoPath));
+ }
+
+ /**
+ * Login API response
+ */
+ public record UserApiResponse(String login, String type) {}
+
+ /**
+ * Setup the mock
+ * @param ssh_url the SSH URL
+ */
+ public record RepoApiResponse(String default_branch, String clone_url, String ssh_url) {}
+
+ /**
+ * Setup the mock
+ * @param plugins
+ */
+ public record PluginStatsApiResponse(Map plugins) {}
+
+ /**
+ * Update center API response
+ * @param plugins the plugins
+ * @param deprecations the deprecations
+ */
+ public record UpdateCenterApiResponse(
+ Map plugins,
+ Map deprecations) {}
+
+ /**
+ * Plugin versions API response
+ * @param plugins the plugins
+ */
+ public record PluginVersionsApiResponse(Map> plugins) {}
+
+ /**
+ * Health score API response
+ * @param plugins the plugins
+ */
+ public record HealthScoreApiResponse(Map plugins) {}
+}
diff --git a/plugin-modernizer-cli/src/test/resources/a-fake-plugin/pom.xml b/plugin-modernizer-cli/src/test/resources/a-fake-plugin/pom.xml
deleted file mode 100644
index e69de29b..00000000
diff --git a/plugin-modernizer-core/pom.xml b/plugin-modernizer-core/pom.xml
index a32ac174..c34e4136 100644
--- a/plugin-modernizer-core/pom.xml
+++ b/plugin-modernizer-core/pom.xml
@@ -55,7 +55,14 @@
org.apache.maven.shared
maven-invoker
- 3.3.0
+
+
+ org.apache.sshd
+ sshd-core
+
+
+ org.apache.sshd
+ sshd-git
org.bouncycastle
@@ -69,6 +76,10 @@
org.eclipse.jgit
org.eclipse.jgit
+
+ org.eclipse.jgit
+ org.eclipse.jgit.ssh.apache
+
org.kohsuke
github-api
diff --git a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Config.java b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Config.java
index b33d69ae..8bf14b7a 100644
--- a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Config.java
+++ b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Config.java
@@ -22,6 +22,7 @@ public class Config {
private final URL githubApiUrl;
private final Path cachePath;
private final Path mavenHome;
+ private final Path mavenLocalRepo;
private final boolean dryRun;
private final boolean draft;
private final boolean removeForks;
@@ -29,6 +30,7 @@ public class Config {
private final Long githubAppId;
private final Long githubAppSourceInstallationId;
private final Long githubAppTargetInstallationId;
+ private final Path sshPrivateKey;
private Config(
String version,
@@ -36,6 +38,7 @@ private Config(
Long githubAppId,
Long githubAppSourceInstallationId,
Long githubAppTargetInstallationId,
+ Path sshPrivateKey,
List plugins,
Recipe recipe,
URL jenkinsUpdateCenter,
@@ -45,6 +48,7 @@ private Config(
URL githubApiUrl,
Path cachePath,
Path mavenHome,
+ Path mavenLocalRepo,
boolean dryRun,
boolean draft,
boolean removeForks) {
@@ -53,6 +57,7 @@ private Config(
this.githubAppId = githubAppId;
this.githubAppSourceInstallationId = githubAppSourceInstallationId;
this.githubAppTargetInstallationId = githubAppTargetInstallationId;
+ this.sshPrivateKey = sshPrivateKey;
this.plugins = plugins;
this.recipe = recipe;
this.jenkinsUpdateCenter = jenkinsUpdateCenter;
@@ -62,6 +67,7 @@ private Config(
this.githubApiUrl = githubApiUrl;
this.cachePath = cachePath;
this.mavenHome = mavenHome;
+ this.mavenLocalRepo = mavenLocalRepo;
this.dryRun = dryRun;
this.draft = draft;
this.removeForks = removeForks;
@@ -87,6 +93,10 @@ public Long getGithubAppTargetInstallationId() {
return githubAppTargetInstallationId;
}
+ public Path getSshPrivateKey() {
+ return sshPrivateKey;
+ }
+
public List getPlugins() {
return plugins;
}
@@ -124,11 +134,21 @@ public URL getGithubApiUrl() {
}
public Path getCachePath() {
- return cachePath;
+ return cachePath.toAbsolutePath();
}
public Path getMavenHome() {
- return mavenHome;
+ if (mavenHome == null) {
+ return null;
+ }
+ return mavenHome.toAbsolutePath();
+ }
+
+ public Path getMavenLocalRepo() {
+ if (mavenLocalRepo == null) {
+ return Settings.DEFAULT_MAVEN_LOCAL_REPO;
+ }
+ return mavenLocalRepo.toAbsolutePath();
}
public boolean isDryRun() {
@@ -157,6 +177,7 @@ public static class Builder {
private Long githubAppId;
private Long githubAppSourceInstallationId;
private Long githubAppTargetInstallationId;
+ private Path sshPrivateKey = Settings.SSH_PRIVATE_KEY;
private List plugins;
private Recipe recipe;
private URL jenkinsUpdateCenter = Settings.DEFAULT_UPDATE_CENTER_URL;
@@ -166,6 +187,7 @@ public static class Builder {
private URL githubApiUrl = Settings.GITHUB_API_URL;
private Path cachePath = Settings.DEFAULT_CACHE_PATH;
private Path mavenHome = Settings.DEFAULT_MAVEN_HOME;
+ private Path mavenLocalRepo = Settings.DEFAULT_MAVEN_LOCAL_REPO;
private boolean dryRun = false;
private boolean draft = false;
public boolean removeForks = false;
@@ -195,6 +217,11 @@ public Builder withGitHubAppTargetInstallationId(Long githubAppInstallationId) {
return this;
}
+ public Builder withSshPrivateKey(Path sshPrivateKey) {
+ this.sshPrivateKey = sshPrivateKey;
+ return this;
+ }
+
public Builder withPlugins(List plugins) {
this.plugins = plugins;
return this;
@@ -254,6 +281,13 @@ public Builder withMavenHome(Path mavenHome) {
return this;
}
+ public Builder withMavenLocalRepo(Path mavenLocalRepo) {
+ if (mavenLocalRepo != null) {
+ this.mavenLocalRepo = mavenLocalRepo;
+ }
+ return this;
+ }
+
public Builder withDryRun(boolean dryRun) {
this.dryRun = dryRun;
return this;
@@ -276,6 +310,7 @@ public Config build() {
githubAppId,
githubAppSourceInstallationId,
githubAppTargetInstallationId,
+ sshPrivateKey,
plugins,
recipe,
jenkinsUpdateCenter,
@@ -285,6 +320,7 @@ public Config build() {
githubApiUrl,
cachePath,
mavenHome,
+ mavenLocalRepo,
dryRun,
draft,
removeForks);
diff --git a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Settings.java b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Settings.java
index 873f5d43..40ed4aca 100644
--- a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Settings.java
+++ b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/config/Settings.java
@@ -41,10 +41,14 @@ public class Settings {
public static final Path DEFAULT_MAVEN_HOME;
+ public static final Path DEFAULT_MAVEN_LOCAL_REPO;
+
public static final String MAVEN_REWRITE_PLUGIN_VERSION;
public static final String GITHUB_TOKEN;
+ public static final Path SSH_PRIVATE_KEY;
+
public static final String GITHUB_OWNER;
public static final Path GITHUB_APP_PRIVATE_KEY_FILE;
@@ -75,19 +79,27 @@ public class Settings {
private Settings() {}
static {
- String cacheBaseDir = System.getProperty("user.home");
- if (cacheBaseDir == null) {
- cacheBaseDir = System.getProperty("user.dir");
+ String userBaseDir = System.getProperty("user.home");
+ if (userBaseDir == null) {
+ userBaseDir = System.getProperty("user.dir");
}
String cacheDirFromEnv = System.getenv("CACHE_DIR");
if (cacheDirFromEnv == null) {
- DEFAULT_CACHE_PATH = Paths.get(cacheBaseDir, ".cache", CACHE_SUBDIR);
+ DEFAULT_CACHE_PATH = Paths.get(userBaseDir, ".cache", CACHE_SUBDIR);
} else {
DEFAULT_CACHE_PATH = Paths.get(cacheDirFromEnv, CACHE_SUBDIR);
}
DEFAULT_MAVEN_HOME = getDefaultMavenHome();
+ DEFAULT_MAVEN_LOCAL_REPO = getDefaultMavenLocalRepo();
MAVEN_REWRITE_PLUGIN_VERSION = getRewritePluginVersion();
+ String sshPrivateKey = System.getenv("SSH_PRIVATE_KEY");
+ if (sshPrivateKey != null) {
+ SSH_PRIVATE_KEY = Paths.get(sshPrivateKey);
+ } else {
+ SSH_PRIVATE_KEY = Paths.get(userBaseDir, ".ssh", "id_rsa");
+ }
+
GITHUB_TOKEN = getGithubToken();
GITHUB_OWNER = getGithubOwner();
GITHUB_APP_PRIVATE_KEY_FILE = getGithubAppPrivateKeyFile();
@@ -157,6 +169,18 @@ private static Path getDefaultMavenHome() {
return Path.of(mavenHome);
}
+ private static Path getDefaultMavenLocalRepo() {
+ String mavenLocalRepo = System.getenv("MAVEN_LOCAL_REPO");
+ if (mavenLocalRepo == null) {
+ String userBaseDir = System.getProperty("user.home");
+ if (userBaseDir == null) {
+ userBaseDir = System.getProperty("user.dir");
+ }
+ return Path.of(userBaseDir, ".m2", "repository").toAbsolutePath();
+ }
+ return Path.of(mavenLocalRepo);
+ }
+
private static @Nullable String getRewritePluginVersion() {
return readProperty("openrewrite.maven.plugin.version", "versions.properties");
}
@@ -259,7 +283,7 @@ public static Path getDefaultSdkManJava(final String key) {
}
public static Path getPluginsDirectory(Plugin plugin) {
- return DEFAULT_CACHE_PATH.resolve(plugin.getName());
+ return plugin.getConfig().getCachePath().resolve(plugin.getName());
}
/**
diff --git a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/github/GHService.java b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/github/GHService.java
index b12b8896..84be55c3 100644
--- a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/github/GHService.java
+++ b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/github/GHService.java
@@ -12,19 +12,22 @@
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
+import org.apache.sshd.git.transport.GitSshdSessionFactory;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.ResetCommand;
import org.eclipse.jgit.api.Status;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
+import org.eclipse.jgit.errors.UnsupportedCredentialItem;
import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.transport.PushResult;
-import org.eclipse.jgit.transport.RefSpec;
-import org.eclipse.jgit.transport.URIish;
-import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
+import org.eclipse.jgit.transport.*;
import org.kohsuke.github.GHApp;
import org.kohsuke.github.GHAppInstallationToken;
import org.kohsuke.github.GHBranchSync;
@@ -61,6 +64,11 @@ public class GHService {
*/
private GHApp app;
+ /**
+ * If the authentication is done using SSH key
+ */
+ private boolean sshKeyAuth = false;
+
/**
* Validate the configuration of the GHService
*/
@@ -68,6 +76,7 @@ public void validate() {
if (config.isFetchMetadataOnly()) {
return;
}
+ setSshKeyAuth();
if (Settings.GITHUB_TOKEN == null
&& (config.getGithubAppId() == null
|| config.getGithubAppSourceInstallationId() == null
@@ -149,6 +158,20 @@ public void connect() {
} catch (IOException e) {
throw new ModernizerException("Failed to connect to GitHub. Cannot use GitHub/SCM integration", e);
}
+ // Ensure to set up SSH client for Git operations
+ setSshKeyAuth();
+ if (sshKeyAuth) {
+ try {
+ SshClient client = SshClient.setUpDefaultClient();
+ FileKeyPairProvider keyPairProvider =
+ new FileKeyPairProvider(Collections.singletonList(config.getSshPrivateKey()));
+ client.setKeyIdentityProvider(keyPairProvider);
+ GitSshdSessionFactory sshdFactory = new GitSshdSessionFactory(client);
+ SshSessionFactory.setInstance(sshdFactory);
+ } catch (Exception e) {
+ throw new ModernizerException("Failed to set up SSH client for Git operations", e);
+ }
+ }
}
/**
@@ -488,8 +511,8 @@ public void fetch(Plugin plugin) {
}
try {
fetchRepository(plugin);
- LOG.debug("Fetched repository from {}", repository.getHtmlUrl());
- } catch (GitAPIException e) {
+ LOG.debug("Fetched repository from {}", repository.getSshUrl());
+ } catch (GitAPIException | URISyntaxException e) {
LOG.error("Failed to fetch the repository", e);
plugin.addError("Failed to fetch the repository", e);
plugin.raiseLastError();
@@ -501,12 +524,27 @@ public void fetch(Plugin plugin) {
* @param plugin The plugin to fetch
* @throws GitAPIException If the fetch operation failed
*/
- private void fetchRepository(Plugin plugin) throws GitAPIException {
+ private void fetchRepository(Plugin plugin) throws GitAPIException, URISyntaxException {
LOG.debug("Fetching {}", plugin.getName());
GHRepository repository = config.isDryRun() || config.isFetchMetadataOnly() || plugin.isArchived(this)
? getRepository(plugin)
: getRepositoryFork(plugin);
- String remoteUrl = repository.getHttpTransportUrl();
+
+ // Get the correct URI
+ URIish remoteUri =
+ sshKeyAuth ? new URIish(repository.getSshUrl()) : new URIish(repository.getHttpTransportUrl());
+
+ // Ensure to set port 22 if not set on remote URL to work with apache mina sshd
+ if (sshKeyAuth) {
+ if (remoteUri.getScheme() == null) {
+ remoteUri = remoteUri.setScheme("ssh");
+ LOG.debug("Setting scheme ssh for remote URI {}", remoteUri);
+ }
+ if (remoteUri.getPort() == -1) {
+ remoteUri = remoteUri.setPort(22);
+ LOG.debug("Setting port 22 for remote URI {}", remoteUri);
+ }
+ }
// Fetch latest changes
if (Files.isDirectory(plugin.getLocalRepository())) {
// Ensure to set the correct remote, reset changes and pull
@@ -516,10 +554,13 @@ private void fetchRepository(Plugin plugin) throws GitAPIException {
: plugin.getRemoteForkRepository(this).getDefaultBranch();
git.remoteSetUrl()
.setRemoteName("origin")
- .setRemoteUri(new URIish(repository.getHttpTransportUrl()))
+ .setRemoteUri(remoteUri)
.call();
- git.fetch().setRemote("origin").call();
- LOG.debug("Resetting changes and pulling latest changes from {}", remoteUrl);
+ git.fetch()
+ .setCredentialsProvider(getCredentialProvider())
+ .setRemote("origin")
+ .call();
+ LOG.debug("Resetting changes and pulling latest changes from {}", remoteUri);
git.reset()
.setMode(ResetCommand.ResetType.HARD)
.setRef("origin/" + defaultBranch)
@@ -530,11 +571,12 @@ private void fetchRepository(Plugin plugin) throws GitAPIException {
.setName(defaultBranch)
.call();
git.pull()
+ .setCredentialsProvider(getCredentialProvider())
.setRemote("origin")
.setRemoteBranchName(defaultBranch)
.call();
- LOG.info("Fetched repository from {} to branch {}", remoteUrl, ref.getName());
- } catch (IOException | URISyntaxException e) {
+ LOG.info("Fetched repository from {} to branch {}", remoteUri, ref.getName());
+ } catch (IOException e) {
plugin.addError("Failed fetch repository", e);
plugin.raiseLastError();
}
@@ -542,10 +584,12 @@ private void fetchRepository(Plugin plugin) throws GitAPIException {
// Clone the repository
else {
try (Git git = Git.cloneRepository()
- .setURI(remoteUrl)
+ .setCredentialsProvider(getCredentialProvider())
+ .setRemote("origin")
+ .setURI(remoteUri.toString())
.setDirectory(plugin.getLocalRepository().toFile())
.call()) {
- LOG.debug("Clone successfully from {}", remoteUrl);
+ LOG.debug("Clone successfully from {}", remoteUri);
}
}
}
@@ -698,9 +742,8 @@ public void pushChanges(Plugin plugin) {
List results = StreamSupport.stream(
git.push()
.setForce(true)
- .setCredentialsProvider(
- new UsernamePasswordCredentialsProvider(Settings.GITHUB_TOKEN, ""))
.setRemote("origin")
+ .setCredentialsProvider(getCredentialProvider())
.setRefSpecs(new RefSpec(BRANCH_NAME + ":" + BRANCH_NAME))
.call()
.spliterator(),
@@ -789,6 +832,16 @@ public void openPullRequest(Plugin plugin) {
}
}
+ /**
+ * Get the current credentials provider
+ * @return The credentials provider
+ */
+ private CredentialsProvider getCredentialProvider() {
+ return sshKeyAuth
+ ? new SshCredentialsProvider()
+ : new UsernamePasswordCredentialsProvider(Settings.GITHUB_TOKEN, "");
+ }
+
/**
* Return if the given repository has any pull request originating from it
* Typically to avoid deleting fork with open pull requests
@@ -852,6 +905,14 @@ public String getGithubOwner() {
: getCurrentUser().getLogin();
}
+ /**
+ * Return if SSH auth is used
+ * @return True if SSH key is used
+ */
+ public boolean isSshKeyAuth() {
+ return sshKeyAuth;
+ }
+
/**
* Ensure the forked reository correspond of the origin parent repository
* @param originalRepo The original repository
@@ -874,4 +935,39 @@ private void checkSameParentRepository(Plugin plugin, GHRepository originalRepo,
plugin);
}
}
+
+ /**
+ * Set the SSH key authentication if needed
+ */
+ private void setSshKeyAuth() {
+ Path privateKey = config.getSshPrivateKey();
+ if (Files.isRegularFile(privateKey)) {
+ sshKeyAuth = true;
+ LOG.debug("Using SSH private key for git operation: {}", privateKey);
+ } else {
+ sshKeyAuth = false;
+ LOG.debug("SSH private key file {} does not exist. Will use GH_TOKEN for git operation", privateKey);
+ }
+ }
+
+ /**
+ * JGit expect a credential provider even if transport and authentication is none at transport level with
+ * Apache Mina SSHD. This is therefor a dummy provider
+ */
+ private static class SshCredentialsProvider extends CredentialsProvider {
+ @Override
+ public boolean isInteractive() {
+ return false;
+ }
+
+ @Override
+ public boolean supports(CredentialItem... credentialItems) {
+ return false;
+ }
+
+ @Override
+ public boolean get(URIish uri, CredentialItem... credentialItems) throws UnsupportedCredentialItem {
+ return false;
+ }
+ }
}
diff --git a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/MavenInvoker.java b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/MavenInvoker.java
index 49da86aa..507ac70d 100644
--- a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/MavenInvoker.java
+++ b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/MavenInvoker.java
@@ -114,6 +114,7 @@ public void invokeRewrite(Plugin plugin) {
private String[] getSingleRecipeArgs(Recipe recipe) {
List goals = new ArrayList<>();
goals.add("org.openrewrite.maven:rewrite-maven-plugin:" + Settings.MAVEN_REWRITE_PLUGIN_VERSION + ":run");
+ goals.add("-Dmaven.repo.local=%s".formatted(config.getMavenLocalRepo()));
goals.add("-Drewrite.activeRecipes=" + recipe.getName());
goals.add("-Drewrite.recipeArtifactCoordinates=io.jenkins.plugin-modernizer:plugin-modernizer-core:"
+ config.getVersion());
@@ -163,10 +164,10 @@ private void validatePom(Plugin plugin) {
}
/**
- * Validate the Maven home directory.
+ * Validate the Maven home and local repo directory.
* @throws IllegalArgumentException if the Maven home directory is not set or invalid.
*/
- public void validateMavenHome() {
+ public void validateMaven() {
Path mavenHome = config.getMavenHome();
if (mavenHome == null) {
throw new ModernizerException(
@@ -176,6 +177,14 @@ public void validateMavenHome() {
if (!Files.isDirectory(mavenHome) || !Files.isExecutable(mavenHome.resolve("bin/mvn"))) {
throw new ModernizerException("Invalid Maven home directory at '%s'.".formatted(mavenHome));
}
+
+ Path mavenLocalRepo = config.getMavenLocalRepo();
+ if (mavenLocalRepo == null) {
+ throw new ModernizerException("Maven local repository is not set.");
+ }
+ if (!Files.isDirectory(mavenLocalRepo)) {
+ throw new ModernizerException("Invalid Maven local repository at '%s'.".formatted(mavenLocalRepo));
+ }
}
/**
diff --git a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/PluginModernizer.java b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/PluginModernizer.java
index 3cb094c2..397447f2 100644
--- a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/PluginModernizer.java
+++ b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/impl/PluginModernizer.java
@@ -38,7 +38,7 @@ public class PluginModernizer {
* Validate the configuration
*/
public void validate() {
- mavenInvoker.validateMavenHome();
+ mavenInvoker.validateMaven();
mavenInvoker.validateMavenVersion();
if (!ghService.isConnected()) {
ghService.connect();
@@ -72,6 +72,14 @@ public String getGithubOwner() {
return ghService.getGithubOwner();
}
+ /**
+ * Expose the effective SSH private key path
+ * @return The SSH private key path
+ */
+ public String getSshPrivateKeyPath() {
+ return config.getSshPrivateKey().toString();
+ }
+
/**
* Expose the effective Maven version
* @return The Maven version
@@ -90,6 +98,14 @@ public String getMavenHome() {
return config.getMavenHome().toString();
}
+ /**
+ * Expose the effective Maven local repository
+ * @return The Maven local repository
+ */
+ public String getMavenLocalRepo() {
+ return config.getMavenLocalRepo().toString();
+ }
+
/**
* Expose the effective cache path
* @return The cache path
@@ -125,11 +141,18 @@ public void start() {
LOG.debug("Plugins: {}", config.getPlugins());
LOG.debug("Recipe: {}", config.getRecipe().getName());
LOG.debug("GitHub owner: {}", config.getGithubOwner());
+ if (ghService.isSshKeyAuth()) {
+ LOG.debug("SSH private key: {}", config.getSshPrivateKey());
+ } else {
+ LOG.debug("Using GitHub token for git authentication");
+ }
LOG.debug("Update Center Url: {}", config.getJenkinsUpdateCenter());
LOG.debug("Plugin versions Url: {}", config.getJenkinsPluginVersions());
LOG.debug("Plugin Health Score Url: {}", config.getPluginHealthScore());
LOG.debug("Installation Stats Url: {}", config.getPluginStatsInstallations());
LOG.debug("Cache Path: {}", config.getCachePath());
+ LOG.debug("Maven Home: {}", config.getMavenHome());
+ LOG.debug("Maven Local Repository: {}", config.getMavenLocalRepo());
LOG.debug("Dry Run: {}", config.isDryRun());
LOG.debug("Maven rewrite plugin version: {}", Settings.MAVEN_REWRITE_PLUGIN_VERSION);
@@ -374,6 +397,7 @@ private void printResults(List plugins) {
LOG.error("Stacktrace: ", error);
}
}
+
}
// Display what's done
else {
diff --git a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginService.java b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginService.java
index 2e609df8..303b3ceb 100644
--- a/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginService.java
+++ b/plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginService.java
@@ -38,7 +38,7 @@ public String extractRepoName(Plugin plugin) {
String scmUrl = updateCenterPlugin.scm();
int lastSlashIndex = scmUrl.lastIndexOf('/');
if (lastSlashIndex != -1 && lastSlashIndex < scmUrl.length() - 1) {
- return scmUrl.substring(lastSlashIndex + 1);
+ return scmUrl.substring(lastSlashIndex + 1).replaceAll(".git$", "");
} else {
plugin.addError("Invalid SCM URL format");
plugin.raiseLastError();
diff --git a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/config/ConfigTest.java b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/config/ConfigTest.java
index 790367fe..d7f644bb 100644
--- a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/config/ConfigTest.java
+++ b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/config/ConfigTest.java
@@ -27,8 +27,8 @@ public void testConfigBuilderWithAllFields() throws MalformedURLException {
Recipe recipe = Mockito.mock(Recipe.class);
Mockito.doReturn("recipe1").when(recipe).getName();
URL jenkinsUpdateCenter = new URL("https://updates.jenkins.io/current/update-center.actual.json");
- Path cachePath = Paths.get("/path/to/cache");
- Path mavenHome = Paths.get("/path/to/maven");
+ Path cachePath = Paths.get("path/to/cache");
+ Path mavenHome = Paths.get("path/to/maven");
boolean dryRun = true;
Config config = Config.builder()
@@ -48,8 +48,8 @@ public void testConfigBuilderWithAllFields() throws MalformedURLException {
assertEquals(plugins, config.getPlugins());
assertEquals(recipe, config.getRecipe());
assertEquals(jenkinsUpdateCenter, config.getJenkinsUpdateCenter());
- assertEquals(cachePath, config.getCachePath());
- assertEquals(mavenHome, config.getMavenHome());
+ assertEquals(cachePath.toAbsolutePath(), config.getCachePath());
+ assertEquals(mavenHome.toAbsolutePath(), config.getMavenHome());
assertTrue(config.isRemoveForks());
assertTrue(config.isRemoveForks());
assertTrue(config.isDryRun());
diff --git a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/github/GHServiceTest.java b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/github/GHServiceTest.java
index 13e52b8c..335f6360 100644
--- a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/github/GHServiceTest.java
+++ b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/github/GHServiceTest.java
@@ -29,6 +29,7 @@
import java.util.List;
import org.eclipse.jgit.api.CloneCommand;
import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.transport.CredentialsProvider;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -55,6 +56,9 @@ public class GHServiceTest {
@TempDir
private Path pluginDir;
+ @TempDir
+ private Path sshDir;
+
/**
* Tested instance
*/
@@ -573,7 +577,46 @@ public void shouldDeleteForkIfAllConditionsMet() throws Exception {
}
@Test
- public void shouldFetchOriginalRepoInDryRunModeToNewFolder() throws Exception {
+ public void shouldSshFetchOriginalRepoInDryRunModeToNewFolder() throws Exception {
+
+ // Mock
+ GHRepository repository = Mockito.mock(GHRepository.class);
+ Git git = Mockito.mock(Git.class);
+ CloneCommand cloneCommand = Mockito.mock(CloneCommand.class);
+
+ // Use SSH key auth
+ Field field = ReflectionUtils.findFields(
+ GHService.class,
+ f -> f.getName().equals("sshKeyAuth"),
+ ReflectionUtils.HierarchyTraversalMode.TOP_DOWN)
+ .get(0);
+ field.setAccessible(true);
+ field.set(service, true);
+
+ doReturn(true).when(config).isDryRun();
+ doReturn("fake-repo").when(plugin).getRepositoryName();
+ doReturn(repository).when(github).getRepository(eq("jenkinsci/fake-repo"));
+ doReturn(git).when(cloneCommand).call();
+ doReturn("fake-url").when(repository).getSshUrl();
+ doReturn(cloneCommand).when(cloneCommand).setRemote(eq("origin"));
+ doReturn(cloneCommand).when(cloneCommand).setURI(eq("ssh:///fake-url"));
+ doReturn(cloneCommand).when(cloneCommand).setCredentialsProvider(any(CredentialsProvider.class));
+ doReturn(cloneCommand).when(cloneCommand).setDirectory(any(File.class));
+
+ // Directory doesn't exists
+ doReturn(Path.of("not-existing-dir")).when(plugin).getLocalRepository();
+
+ // Test
+ try (MockedStatic mockStaticGit = mockStatic(Git.class)) {
+ mockStaticGit.when(Git::cloneRepository).thenReturn(cloneCommand);
+ service.fetch(plugin);
+ verify(cloneCommand, times(1)).call();
+ verifyNoMoreInteractions(cloneCommand);
+ }
+ }
+
+ @Test
+ public void shouldHttpFetchOriginalRepoInDryRunModeToNewFolder() throws Exception {
// Mock
GHRepository repository = Mockito.mock(GHRepository.class);
@@ -585,7 +628,9 @@ public void shouldFetchOriginalRepoInDryRunModeToNewFolder() throws Exception {
doReturn(repository).when(github).getRepository(eq("jenkinsci/fake-repo"));
doReturn(git).when(cloneCommand).call();
doReturn("fake-url").when(repository).getHttpTransportUrl();
+ doReturn(cloneCommand).when(cloneCommand).setRemote(eq("origin"));
doReturn(cloneCommand).when(cloneCommand).setURI(eq("fake-url"));
+ doReturn(cloneCommand).when(cloneCommand).setCredentialsProvider(any(CredentialsProvider.class));
doReturn(cloneCommand).when(cloneCommand).setDirectory(any(File.class));
// Directory doesn't exists
@@ -601,21 +646,65 @@ public void shouldFetchOriginalRepoInDryRunModeToNewFolder() throws Exception {
}
@Test
- public void shouldFetchOriginalRepoInMetaDataOnlyModeToNewFolder() throws Exception {
+ public void shouldSshFetchOriginalRepoInMetaDataOnlyModeToNewFolder() throws Exception {
// Mock
GHRepository repository = Mockito.mock(GHRepository.class);
Git git = Mockito.mock(Git.class);
CloneCommand cloneCommand = Mockito.mock(CloneCommand.class);
+ // Use SSH key auth
+ Field field = ReflectionUtils.findFields(
+ GHService.class,
+ f -> f.getName().equals("sshKeyAuth"),
+ ReflectionUtils.HierarchyTraversalMode.TOP_DOWN)
+ .get(0);
+ field.setAccessible(true);
+ field.set(service, true);
+
doReturn(false).when(config).isDryRun();
+
+ doReturn(true).when(config).isFetchMetadataOnly();
+ doReturn("fake-repo").when(plugin).getRepositoryName();
+ doReturn(repository).when(github).getRepository(eq("jenkinsci/fake-repo"));
+ doReturn(git).when(cloneCommand).call();
+ doReturn("fake-url").when(repository).getSshUrl();
+ doReturn(cloneCommand).when(cloneCommand).setRemote(eq("origin"));
+ doReturn(cloneCommand).when(cloneCommand).setURI(eq("ssh:///fake-url"));
+ doReturn(cloneCommand).when(cloneCommand).setDirectory(any(File.class));
+ doReturn(cloneCommand).when(cloneCommand).setCredentialsProvider(any(CredentialsProvider.class));
+
+ // Directory doesn't exists
+ doReturn(Path.of("not-existing-dir")).when(plugin).getLocalRepository();
+
+ // Test
+ try (MockedStatic mockStaticGit = mockStatic(Git.class)) {
+ mockStaticGit.when(Git::cloneRepository).thenReturn(cloneCommand);
+ service.fetch(plugin);
+ verify(cloneCommand, times(1)).call();
+ verifyNoMoreInteractions(cloneCommand);
+ }
+ }
+
+ @Test
+ public void shouldHttpFetchOriginalRepoInMetaDataOnlyModeToNewFolder() throws Exception {
+
+ // Mock
+ GHRepository repository = Mockito.mock(GHRepository.class);
+ Git git = Mockito.mock(Git.class);
+ CloneCommand cloneCommand = Mockito.mock(CloneCommand.class);
+
+ doReturn(false).when(config).isDryRun();
+
doReturn(true).when(config).isFetchMetadataOnly();
doReturn("fake-repo").when(plugin).getRepositoryName();
doReturn(repository).when(github).getRepository(eq("jenkinsci/fake-repo"));
doReturn(git).when(cloneCommand).call();
doReturn("fake-url").when(repository).getHttpTransportUrl();
+ doReturn(cloneCommand).when(cloneCommand).setRemote(eq("origin"));
doReturn(cloneCommand).when(cloneCommand).setURI(eq("fake-url"));
doReturn(cloneCommand).when(cloneCommand).setDirectory(any(File.class));
+ doReturn(cloneCommand).when(cloneCommand).setCredentialsProvider(any(CredentialsProvider.class));
// Directory doesn't exists
doReturn(Path.of("not-existing-dir")).when(plugin).getLocalRepository();
diff --git a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/model/PluginTest.java b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/model/PluginTest.java
index a79bc51d..1b79cdf5 100644
--- a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/model/PluginTest.java
+++ b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/model/PluginTest.java
@@ -14,6 +14,7 @@
import io.jenkins.tools.pluginmodernizer.core.config.Settings;
import io.jenkins.tools.pluginmodernizer.core.github.GHService;
import io.jenkins.tools.pluginmodernizer.core.impl.MavenInvoker;
+import java.nio.file.Path;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
@@ -50,11 +51,30 @@ public void testRepositoryName() {
}
@Test
- public void testLocalRepository() {
- Plugin plugin = Plugin.build("example");
+ public void testDefaultLocalRepository() {
+ Plugin plugin = mock(Plugin.class);
+ doReturn("example").when(plugin).getName();
+ Config config = mock(Config.class);
+ doReturn(Settings.DEFAULT_CACHE_PATH).when(config).getCachePath();
+ doReturn(config).when(plugin).getConfig();
+ assertEquals(
+ Settings.getPluginsDirectory(plugin).resolve("sources").toString(),
+ Settings.DEFAULT_CACHE_PATH
+ .resolve("example")
+ .resolve("sources")
+ .toString());
+ }
+
+ @Test
+ public void testCustomLocalRepository() {
+ Plugin plugin = mock(Plugin.class);
+ doReturn("example").when(plugin).getName();
+ Config config = mock(Config.class);
+ doReturn(Path.of("my-cache")).when(config).getCachePath();
+ doReturn(config).when(plugin).getConfig();
assertEquals(
Settings.getPluginsDirectory(plugin).resolve("sources").toString(),
- plugin.getLocalRepository().toString());
+ Path.of("my-cache").resolve("example").resolve("sources").toString());
}
@Test
diff --git a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginServiceTest.java b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginServiceTest.java
index c97546a2..ba3ad6cf 100644
--- a/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginServiceTest.java
+++ b/plugin-modernizer-core/src/test/java/io/jenkins/tools/pluginmodernizer/core/utils/PluginServiceTest.java
@@ -62,6 +62,10 @@ public void setup() throws Exception {
"valid-plugin",
new UpdateCenterData.UpdateCenterPlugin(
"valid-plugin", "1.0", "https://github.com/jenkinsci/valid-url", "main", "gav", null));
+ updateCenterPlugins.put(
+ "valid-plugin-2",
+ new UpdateCenterData.UpdateCenterPlugin(
+ "valid-plugin", "1.0", "git@github.com/jenkinsci/valid-git-repo.git", "main", "gav", null));
updateCenterPlugins.put(
"invalid-plugin",
new UpdateCenterData.UpdateCenterPlugin(
@@ -116,6 +120,14 @@ public void shouldExtractRepoName() throws Exception {
assertEquals("valid-url", result);
}
+ @Test
+ public void shouldExtractRepoNameWithGitSuffix() throws Exception {
+ setupUpdateCenterMocks();
+ PluginService service = getService();
+ String result = service.extractRepoName(Plugin.build("valid-plugin-2").withConfig(config));
+ assertEquals("valid-git-repo", result);
+ }
+
@Test
public void shouldDownloadPluginVersionDataUpdateCenterData(WireMockRuntimeInfo wmRuntimeInfo) throws Exception {
diff --git a/pom.xml b/pom.xml
index 48e27fa7..b494fbf0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -77,6 +77,8 @@
3.5.2
1.20.4
0.10.0
+ 2.14.0
+ 3.3.0
@@ -197,6 +199,21 @@
maven-artifact
${maven.version}
+
+ org.apache.maven.shared
+ maven-invoker
+ ${maven.invoker.version}
+
+
+ org.apache.sshd
+ sshd-core
+ ${apache.mina.version}
+
+
+ org.apache.sshd
+ sshd-git
+ ${apache.mina.version}
+
org.bouncycastle
bcpkix-jdk18on
@@ -207,6 +224,11 @@
bcprov-jdk18on
${bouncycastle.version}
+
+ org.bouncycastle
+ bcutil-jdk18on
+ ${bouncycastle.version}
+
org.checkerframework
checker-qual
@@ -217,6 +239,11 @@
org.eclipse.jgit
${jgit.version}
+
+ org.eclipse.jgit
+ org.eclipse.jgit.ssh.apache
+ ${jgit.version}
+
org.kohsuke
github-api
@@ -283,6 +310,11 @@
junit-jupiter-engine
test
+
+ org.junit.jupiter
+ junit-jupiter-params
+ test
+
org.mockito
mockito-core