From 5231330b09a875d0b5c85c88e0a80927cc8efe92 Mon Sep 17 00:00:00 2001 From: Andy Damevin Date: Fri, 22 Sep 2023 20:22:24 +0200 Subject: [PATCH] Replaces flacky Maven Frontend Plugin download manager by Vertx to install Node.js (#513) * 2.2.0.CR1 * Try use vertx webclient to install Node.js --- .../lib/PackageManagerInstallFactory.java | 54 +++++++++++++++ .../frontend/lib/VertxFileDownloader.java | 68 +++++++++++++++++++ .../packagemanager/PackageManagerInstall.java | 68 +++++++++++++------ 3 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/PackageManagerInstallFactory.java create mode 100644 deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/VertxFileDownloader.java diff --git a/deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/PackageManagerInstallFactory.java b/deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/PackageManagerInstallFactory.java new file mode 100644 index 00000000..fb098088 --- /dev/null +++ b/deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/PackageManagerInstallFactory.java @@ -0,0 +1,54 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import java.io.File; +import java.nio.file.Path; + +import io.vertx.core.Vertx; + +public class PackageManagerInstallFactory { + + private static final Platform defaultPlatform = Platform.guess(); + private static final String DEFAULT_CACHE_PATH = "cache"; + private final Vertx vertx; + private final Path installDirectory; + private final CacheResolver cacheResolver; + private final VertxFileDownloader fileDownloader; + + public PackageManagerInstallFactory(Vertx vertx, Path installDirectory) { + this.vertx = vertx; + this.installDirectory = installDirectory; + this.cacheResolver = getDefaultCacheResolver(installDirectory); + fileDownloader = new VertxFileDownloader(vertx); + } + + public NodeInstaller getNodeInstaller() { + + return new NodeInstaller(this.getInstallConfig(), new DefaultArchiveExtractor(), fileDownloader); + } + + public NPMInstaller getNPMInstaller() { + return new NPMInstaller(this.getInstallConfig(), new DefaultArchiveExtractor(), fileDownloader); + } + + public PnpmInstaller getPnpmInstaller() { + return new PnpmInstaller(this.getInstallConfig(), new DefaultArchiveExtractor(), fileDownloader); + } + + public YarnInstaller getYarnInstaller() { + return new YarnInstaller(this.getInstallConfig(), new DefaultArchiveExtractor(), fileDownloader); + } + + private NodeExecutorConfig getExecutorConfig() { + return new InstallNodeExecutorConfig(this.getInstallConfig()); + } + + private InstallConfig getInstallConfig() { + final File installDirFile = this.installDirectory.toFile(); + return new DefaultInstallConfig(installDirFile, installDirFile, this.cacheResolver, defaultPlatform); + } + + private static final CacheResolver getDefaultCacheResolver(Path root) { + return new DirectoryCacheResolver(root.resolve("cache").toFile()); + } + +} diff --git a/deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/VertxFileDownloader.java b/deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/VertxFileDownloader.java new file mode 100644 index 00000000..a88f5bd1 --- /dev/null +++ b/deployment/src/main/java/com/github/eirslett/maven/plugins/frontend/lib/VertxFileDownloader.java @@ -0,0 +1,68 @@ +package com.github.eirslett.maven.plugins.frontend.lib; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FilenameUtils; +import org.jboss.logging.Logger; + +import io.vertx.core.Future; +import io.vertx.core.Vertx; +import io.vertx.core.buffer.Buffer; +import io.vertx.ext.web.client.HttpRequest; +import io.vertx.ext.web.client.HttpResponse; +import io.vertx.ext.web.client.WebClient; +import io.vertx.ext.web.client.WebClientOptions; + +public class VertxFileDownloader implements FileDownloader { + private static final Logger LOG = Logger.getLogger(VertxFileDownloader.class); + private final Vertx vertx; + private WebClient webClient; + + public VertxFileDownloader(Vertx vertx) { + this.vertx = vertx; + webClient = WebClient.create(vertx, new WebClientOptions() + .setSsl(true) + .setFollowRedirects(true) + .setTrustAll(true) + .setKeepAlive(true)); + } + + public void download(String downloadUrl, String destination, String userName, String password) throws DownloadException { + System.setProperty("https.protocols", "TLSv1.2"); + String fixedDownloadUrl = downloadUrl; + + try { + fixedDownloadUrl = FilenameUtils.separatorsToUnix(fixedDownloadUrl); + URI downloadURI = new URI(fixedDownloadUrl); + final Path destinationPath = Path.of(destination); + if ("file".equalsIgnoreCase(downloadURI.getScheme())) { + Files.copy(Paths.get(downloadURI), destinationPath); + } else { + final HttpRequest request = webClient.getAbs(downloadUrl); + final CountDownLatch latch = new CountDownLatch(1); + Future> future = request.send(); + future.onComplete((r) -> latch.countDown()); + latch.await(5, TimeUnit.MINUTES); + if (future.succeeded()) { + Files.write(destinationPath, future.result().body().getBytes()); + } else { + throw new DownloadException("Could not download " + downloadUrl, future.cause()); + } + } + + } catch (URISyntaxException | IOException e) { + throw new DownloadException("Could not download " + downloadUrl, e); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + +} diff --git a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerInstall.java b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerInstall.java index 31fd0d52..16c13e5e 100644 --- a/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerInstall.java +++ b/deployment/src/main/java/io/quarkiverse/quinoa/deployment/packagemanager/PackageManagerInstall.java @@ -1,22 +1,24 @@ package io.quarkiverse.quinoa.deployment.packagemanager; +import static io.vertx.core.spi.resolver.ResolverProvider.DISABLE_DNS_RESOLVER_PROP_NAME; + import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.DirectoryNotEmptyException; import java.nio.file.Path; -import java.util.Collections; import java.util.Optional; import java.util.Set; import org.jboss.logging.Logger; -import com.github.eirslett.maven.plugins.frontend.lib.FrontendPluginFactory; import com.github.eirslett.maven.plugins.frontend.lib.InstallationException; -import com.github.eirslett.maven.plugins.frontend.lib.ProxyConfig; +import com.github.eirslett.maven.plugins.frontend.lib.PackageManagerInstallFactory; import io.quarkiverse.quinoa.deployment.config.PackageManagerInstallConfig; import io.quarkus.deployment.util.FileUtil; import io.quarkus.runtime.configuration.ConfigurationException; +import io.vertx.core.Vertx; +import io.vertx.core.VertxOptions; public final class PackageManagerInstall { @@ -33,7 +35,6 @@ private PackageManagerInstall() { public static Installation install(PackageManagerInstallConfig config, final Path projectDir) { Path installDir = resolveInstallDir(config, projectDir).normalize(); - FrontendPluginFactory factory = new FrontendPluginFactory(null, installDir.toFile()); if (config.nodeVersion().isEmpty()) { throw new ConfigurationException("node-version is required to install package manager", Set.of("quarkus.quinoa.package-manager-install.node-version")); @@ -44,28 +45,53 @@ public static Installation install(PackageManagerInstallConfig config, final Pat } int i = 0; Exception thrown = null; - while (i < 5) { - try { - if (i > 0) { - LOG.infof("An error occurred during the previous Node.js install, retrying (%s/5)", i + 1); - FileUtil.deleteDirectory(installDir); + Vertx vertx = null; + try { + vertx = createVertxInstance(); + PackageManagerInstallFactory factory = new PackageManagerInstallFactory(vertx, installDir); + while (i < 5) { + try { + if (i > 0) { + LOG.warnf("An error occurred '%s' during the previous Node.js install, retrying (%s/5)", + thrown.getCause().getMessage(), i + 1); + FileUtil.deleteDirectory(installDir); + } + return attemptInstall(config, installDir, factory); + } catch (InstallationException e) { + thrown = e; + i++; + } catch (IOException e) { + throw new UncheckedIOException(e); } - return attemptInstall(config, installDir, factory); - } catch (InstallationException e) { - thrown = e; - i++; - } catch (IOException e) { - throw new UncheckedIOException(e); } + } finally { + vertx.close(); } + throw new RuntimeException("Error while installing NodeJS", thrown); } + private static Vertx createVertxInstance() { + String originalValue = System.getProperty(DISABLE_DNS_RESOLVER_PROP_NAME); + Vertx vertx; + try { + System.setProperty(DISABLE_DNS_RESOLVER_PROP_NAME, "true"); + vertx = Vertx.vertx(new VertxOptions()); + } finally { + // Restore the original value + if (originalValue == null) { + System.clearProperty(DISABLE_DNS_RESOLVER_PROP_NAME); + } else { + System.setProperty(DISABLE_DNS_RESOLVER_PROP_NAME, originalValue); + } + } + return vertx; + } + private static Installation attemptInstall(PackageManagerInstallConfig config, Path installDir, - FrontendPluginFactory factory) throws InstallationException { - final ProxyConfig proxy = new ProxyConfig(Collections.emptyList()); + PackageManagerInstallFactory factory) throws InstallationException { try { - factory.getNodeInstaller(proxy) + factory.getNodeInstaller() .setNodeVersion("v" + config.nodeVersion().get()) .setNodeDownloadRoot(config.nodeDownloadRoot()) .setNpmVersion(config.npmVersion()) @@ -84,7 +110,7 @@ private static Installation attemptInstall(PackageManagerInstallConfig config, P final String npmVersion = config.npmVersion(); boolean isNpmProvided = PackageManagerInstallConfig.NPM_PROVIDED.equalsIgnoreCase(npmVersion); if (!isNpmProvided) { - factory.getNPMInstaller(proxy) + factory.getNPMInstaller() .setNodeVersion("v" + config.nodeVersion().get()) .setNpmVersion(npmVersion) .setNpmDownloadRoot(config.npmDownloadRoot()) @@ -95,7 +121,7 @@ private static Installation attemptInstall(PackageManagerInstallConfig config, P final Optional yarnVersion = config.yarnVersion(); if (yarnVersion.isPresent() && isNpmProvided) { executionPath = YARN_PATH; - factory.getYarnInstaller(proxy) + factory.getYarnInstaller() .setYarnVersion("v" + config.yarnVersion().get()) .setYarnDownloadRoot(config.yarnDownloadRoot()) .setIsYarnBerry(true) @@ -106,7 +132,7 @@ private static Installation attemptInstall(PackageManagerInstallConfig config, P final Optional pnpmVersion = config.pnpmVersion(); if (pnpmVersion.isPresent() && isNpmProvided && yarnVersion.isEmpty()) { executionPath = PNPM_PATH; - factory.getPnpmInstaller(proxy) + factory.getPnpmInstaller() .setNodeVersion("v" + config.nodeVersion().get()) .setPnpmVersion(pnpmVersion.get()) .setPnpmDownloadRoot(config.pnpmDownloadRoot())