diff --git a/jkube-kit/build/service/buildpacks/pom.xml b/jkube-kit/build/service/buildpacks/pom.xml index a6ac725fc4..aaaa882d12 100644 --- a/jkube-kit/build/service/buildpacks/pom.xml +++ b/jkube-kit/build/service/buildpacks/pom.xml @@ -29,5 +29,43 @@ JKube Kit :: Build :: Service :: Buildpacks + + org.eclipse.jkube + jkube-kit-build-api + + + org.eclipse.jkube + jkube-kit-common + test + test-jar + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.mockito + mockito-core + + + org.assertj + assertj-core + + + + + + + src/main/resources-filtered + true + + + src/main/resources + false + + + + diff --git a/jkube-kit/build/service/buildpacks/src/main/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCliDownloader.java b/jkube-kit/build/service/buildpacks/src/main/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCliDownloader.java new file mode 100644 index 0000000000..44ae9054ae --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/main/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCliDownloader.java @@ -0,0 +1,144 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jkube.kit.common.KitLogger; +import org.eclipse.jkube.kit.common.util.FileUtil; + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicReference; + +import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; +import static org.eclipse.jkube.kit.common.util.EnvUtil.findBinaryFileInUserPath; +import static org.eclipse.jkube.kit.common.util.EnvUtil.getProcessorArchitecture; +import static org.eclipse.jkube.kit.common.util.EnvUtil.isMacOs; +import static org.eclipse.jkube.kit.common.util.EnvUtil.isWindows; +import static org.eclipse.jkube.kit.common.util.EnvUtil.getUserHome; +import static org.eclipse.jkube.kit.common.util.IoUtil.downloadArchive; +import static org.eclipse.jkube.kit.common.util.SemanticVersionUtil.removeBuildMetadata; + +public class BuildPackCliDownloader { + private static final String JKUBE_PACK_DIR = ".jkube"; + private static final String PACK_UNIX_CLI_NAME = "pack"; + private static final String PACK_DEFAULT_CLI_VERSION_PROPERTY = "version"; + private static final String PACK_CLI_LINUX_ARTIFACT = "linux.artifact"; + private static final String PACK_CLI_LINUX_ARM64_ARTIFACT = "linux-arm64.artifact"; + private static final String PACK_CLI_MACOS_ARTIFACT = "macos.artifact"; + private static final String PACK_CLI_MACOS_ARM64_ARTIFACT = "macos-arm64.artifact"; + private static final String PACK_CLI_WINDOWS_ARTIFACT = "windows.artifact"; + + private final KitLogger kitLogger; + private final String packCliVersion; + private final Properties packProperties; + private final File jKubeUserHomeDir; + + public BuildPackCliDownloader(KitLogger kitLogger, Properties packProperties) { + this.kitLogger = kitLogger; + this.packProperties = packProperties; + packCliVersion = (String) packProperties.get(PACK_DEFAULT_CLI_VERSION_PROPERTY); + jKubeUserHomeDir = new File(getUserHome(), JKUBE_PACK_DIR); + } + + public File getPackCLIIfPresentOrDownload() { + try { + File pack = resolveBinaryLocation().toFile(); + if (!(pack.exists() && isValid(pack))) { + downloadPackCli(); + } + return pack; + } catch (IOException ioException) { + kitLogger.warn("Not able to download pack CLI : " + ioException.getMessage()); + kitLogger.info("Checking for local pack CLI"); + return getLocalPackCLI(); + } + } + + private void downloadPackCli() throws IOException { + File tempDownloadDirectory = FileUtil.createTempDirectory(); + FileUtil.createDirectory(jKubeUserHomeDir); + URL downloadUrl = new URL(inferApplicableDownloadArtifactUrl()); + Path packInJKubeDir = resolveBinaryLocation(); + kitLogger.info("Downloading pack CLI %s", packCliVersion); + + downloadArchive(downloadUrl, tempDownloadDirectory); + + File packInExtractedArchive = new File(tempDownloadDirectory, packInJKubeDir.toFile().getName()); + if (!packInExtractedArchive.exists()) { + throw new IllegalStateException("Unable to find " + packInJKubeDir.toFile().getName() + " in downloaded artifact"); + } + if (!packInExtractedArchive.canExecute() && !packInExtractedArchive.setExecutable(true)) { + throw new IllegalStateException("Failure in setting execute permission in " + packInExtractedArchive.getAbsolutePath()); + } + Files.copy(packInExtractedArchive.toPath(), packInJKubeDir, REPLACE_EXISTING, COPY_ATTRIBUTES); + FileUtil.cleanDirectory(tempDownloadDirectory); + } + + private String inferApplicableDownloadArtifactUrl() { + boolean isProcessorArchitectureArm = getProcessorArchitecture().equals("aarch64"); + if (isWindows()) { + return (String) packProperties.get(PACK_CLI_WINDOWS_ARTIFACT); + } else if (isMacOs() && isProcessorArchitectureArm) { + return (String) packProperties.get(PACK_CLI_MACOS_ARM64_ARTIFACT); + } else if(isMacOs()) { + return (String) packProperties.get(PACK_CLI_MACOS_ARTIFACT); + } else if (isProcessorArchitectureArm) { + return (String) packProperties.get(PACK_CLI_LINUX_ARM64_ARTIFACT); + } + return (String) packProperties.get(PACK_CLI_LINUX_ARTIFACT); + } + + private File getLocalPackCLI() { + File packCliFoundOnUserPath = checkPackCLIPresentOnMachine(); + if (packCliFoundOnUserPath == null) { + throw new IllegalStateException("No local pack binary found"); + } + return packCliFoundOnUserPath; + } + + private File checkPackCLIPresentOnMachine() { + File packCliFoundOnUserPath = findBinaryFileInUserPath(resolveBinaryLocation().toFile().getName()); + if (packCliFoundOnUserPath != null && isValid(packCliFoundOnUserPath)) { + return packCliFoundOnUserPath; + } + return null; + } + + public boolean isValid(File packCli) { + AtomicReference versionRef = new AtomicReference<>(); + BuildPackCommand versionCommand = new BuildPackCommand(kitLogger, packCli, Collections.singletonList("--version"), versionRef::set); + try { + versionCommand.execute(); + String version = removeBuildMetadata(versionRef.get()); + return StringUtils.isNotBlank(version) && packCliVersion.equals(version); + } catch (IOException e) { + return false; + } + } + + private Path resolveBinaryLocation() { + String binaryName = PACK_UNIX_CLI_NAME; + if (isWindows()) { + binaryName = PACK_UNIX_CLI_NAME + "." + packProperties.getProperty("windows.binary-extension"); + } + return jKubeUserHomeDir.toPath().resolve(binaryName); + } +} \ No newline at end of file diff --git a/jkube-kit/build/service/buildpacks/src/main/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCommand.java b/jkube-kit/build/service/buildpacks/src/main/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCommand.java new file mode 100644 index 0000000000..5c99e6dbf0 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/main/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCommand.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import org.eclipse.jkube.kit.common.ExternalCommand; +import org.eclipse.jkube.kit.common.KitLogger; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + +public class BuildPackCommand extends ExternalCommand { + private final File packCli; + private final List commandLineArgs; + private final StringBuilder errorBuilder; + private final Consumer commandOutputConsumer; + + public BuildPackCommand(KitLogger log, File packCli, List cmdArgs, Consumer outputConsumer) { + super(log); + this.packCli = packCli; + this.commandLineArgs = cmdArgs; + this.commandOutputConsumer = outputConsumer; + this.errorBuilder = new StringBuilder(); + } + + @Override + public String[] getArgs() { + List args = new ArrayList<>(); + args.add(packCli.getAbsolutePath()); + args.addAll(commandLineArgs); + return args.toArray(new String[0]); + } + + @Override + public void processLine(String line) { + commandOutputConsumer.accept(line); + } + + @Override + public void processError(String error) { + errorBuilder.append(error); + } + + public String getError() { + return errorBuilder.toString(); + } + + public int getExitCode() { + return getStatusCode(); + } +} \ No newline at end of file diff --git a/jkube-kit/build/service/buildpacks/src/main/resources-filtered/META-INF/jkube/pack-cli.properties b/jkube-kit/build/service/buildpacks/src/main/resources-filtered/META-INF/jkube/pack-cli.properties new file mode 100644 index 0000000000..72b33b2257 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/main/resources-filtered/META-INF/jkube/pack-cli.properties @@ -0,0 +1,21 @@ +# +# Copyright (c) 2019 Red Hat, Inc. +# This program and the accompanying materials are made +# available under the terms of the Eclipse Public License 2.0 +# which is available at: +# +# https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# Red Hat, Inc. - initial API and implementation +# + +version=${pack.version} +linux.artifact=${pack.linux.artifact} +linux-arm64.artifact=${pack.linux-arm64.artifact} +macos.artifact=${pack.macos.artifact} +macos-arm64.artifact=${pack.macos-arm64.artifact} +windows.artifact=${pack.windows.pack.artifact} +windows.binary-extension=${pack.windows.binary-extension} \ No newline at end of file diff --git a/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/AbstractBuildPackCliDownloaderTest.java b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/AbstractBuildPackCliDownloaderTest.java new file mode 100644 index 0000000000..27f056e526 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/AbstractBuildPackCliDownloaderTest.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; + +import org.eclipse.jkube.kit.common.KitLogger; + +import org.eclipse.jkube.kit.common.TestHttpStaticServer; +import org.eclipse.jkube.kit.common.util.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; + +import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES; +import static org.assertj.core.api.Assertions.assertThatIllegalStateException; +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +abstract class AbstractBuildPackCliDownloaderTest { + private static final String TEST_PACK_VERSION = "0.32.1"; + private KitLogger kitLogger; + @TempDir + private File temporaryFolder; + private File remoteDirectory; + private File oldPackCliInJKubeDir; + private TestHttpStaticServer server; + private BuildPackCliDownloader buildPackCliDownloader; + + abstract String getApplicablePackBinary(); + abstract String getInvalidApplicablePackBinary(); + abstract String getPlatform(); + abstract String getProcessorArchitecture(); + + @BeforeEach + void setUp() { + kitLogger = spy(new KitLogger.SilentLogger()); + Map overriddenSystemProperties = new HashMap<>(); + Map overriddenEnvironmentVariables = new HashMap<>(); + overriddenSystemProperties.put("user.home", temporaryFolder.getAbsolutePath()); + overriddenSystemProperties.put("os.name", getPlatform()); + overriddenSystemProperties.put("os.arch", getProcessorArchitecture()); + overriddenEnvironmentVariables.put("HOME", temporaryFolder.getAbsolutePath()); + overriddenEnvironmentVariables.put("PATH", temporaryFolder.toPath().resolve("bin").toFile().getAbsolutePath()); + EnvUtil.overridePropertyGetter(overriddenSystemProperties::get); + EnvUtil.overrideEnvGetter(overriddenEnvironmentVariables::get); + remoteDirectory = new File(Objects.requireNonNull(getClass().getResource("/artifacts")).getFile()); + server = new TestHttpStaticServer(remoteDirectory); + buildPackCliDownloader = new BuildPackCliDownloader(kitLogger, createPackProperties(server)); + } + + @AfterEach + void tearDown() throws IOException { + server.close(); + EnvUtil.overridePropertyGetter(System::getProperty); + EnvUtil.overrideEnvGetter(System::getenv); + } + + @Test + @DisplayName("pack binary exists and is valid, then use already downloaded cli") + void givenOldPackCliExistsAndIsValid_thenAlreadyExistingPackCliReturned() throws IOException { + // Given + oldPackCliInJKubeDir = new File(Objects.requireNonNull(getClass().getResource(String.format("/%s", getApplicablePackBinary()))).getFile()); + givenPackCliAlreadyDownloaded(); + + // When + File downloadedCli = buildPackCliDownloader.getPackCLIIfPresentOrDownload(); + + // Then + assertThat(downloadedCli.toPath()).isEqualTo(temporaryFolder.toPath().resolve(".jkube").resolve(getApplicablePackBinary())); + assertThat(downloadedCli).hasSameTextualContentAs(oldPackCliInJKubeDir); + } + + @SuppressWarnings("unused") + @Nested + @DisplayName("pack binary doesn't exist") + class PackBinaryDoesNotExist { + @Nested + @DisplayName("download initiated due to no existing pack binary") + class DownloadDueToNoExistingPackBinary extends PackBinaryDownload { } + } + + @Nested + @DisplayName("pack binary exists in .jkube but is not valid") + class PackBinaryExistsButIsNotValid { + @BeforeEach + void setUp() throws IOException { + oldPackCliInJKubeDir = new File(Objects.requireNonNull(getClass().getResource(String.format("/%s", getInvalidApplicablePackBinary()))).getFile()); + givenPackCliAlreadyDownloaded(); + } + + @SuppressWarnings("unused") + @Nested + @DisplayName("download initiated due to invalid existing pack binary") + class DownloadDueToInvalidPackBinary extends PackBinaryDownload { } + } + + private abstract class PackBinaryDownload { + @Nested + @DisplayName("download succeeds") + class DownloadSucceeds { + private File pack; + + @BeforeEach + void download() { + pack = buildPackCliDownloader.getPackCLIIfPresentOrDownload(); + } + + @Test + @DisplayName("copy downloaded binary to user's .jkube folder and return path") + void jKubeDirPathIsReturned() { + assertThat(pack.toPath()) + .isEqualTo(temporaryFolder.toPath().resolve(".jkube").resolve(getApplicablePackBinary())); + } + + @Test + @DisplayName("copied downloaded binary exists and has the right size") + void fileExistsAndHasTheRightSize() { + assertThat(pack) + .exists() + .satisfies(p -> assertThat(p).isNotEmpty()); + } + } + + @Nested + @DisplayName("download fails") + class DownloadFails { + @BeforeEach + void setUp() { + remoteDirectory = new File(Objects.requireNonNull(getClass().getResource("/invalid-artifacts")).getFile()); + server = new TestHttpStaticServer(remoteDirectory); + buildPackCliDownloader = new BuildPackCliDownloader(kitLogger, createPackProperties(server)); + } + + @Test + @DisplayName("warning is logged indicating that pack binary download failed") + void logWarningAboutDownloadFailure() { + // Given + When + assertThatIllegalStateException().isThrownBy(buildPackCliDownloader::getPackCLIIfPresentOrDownload); + + // Then + ArgumentCaptor downloadFailureMessage = ArgumentCaptor.forClass(String.class); + verify(kitLogger).warn(downloadFailureMessage.capture()); + assertThat(downloadFailureMessage.getValue()).contains("Not able to download pack CLI : "); + } + + @Test + @DisplayName("info is logged indicating we attempt to use a local pack binary as a fallback") + void logInfoIndicatingFallbackToLocalPackCli() { + // Given + When + assertThatIllegalStateException().isThrownBy(buildPackCliDownloader::getPackCLIIfPresentOrDownload); + + // Then + verify(kitLogger).info("Checking for local pack CLI"); + } + + @Test + @DisplayName("local pack binary doesn't exist, then throw exception") + void givenNoLocalPackBinaryInUserPath_thenThrowException() { + // When + Then + assertThatIllegalStateException() + .isThrownBy(buildPackCliDownloader::getPackCLIIfPresentOrDownload) + .withMessage("No local pack binary found"); + } + + @Test + @DisplayName("local pack binary exists and is valid, then return local pack binary path") + void givenLocalPackCliExistsAndIsValid_thenReturnPathToLocalPackBinary() throws IOException { + // Given + givenPackCliPresentOnUserPath(String.format("/%s", getApplicablePackBinary())); + + // When + File downloadedCli = buildPackCliDownloader.getPackCLIIfPresentOrDownload(); + + // Then + assertThat(downloadedCli).isEqualTo(temporaryFolder.toPath().resolve("bin").resolve(getApplicablePackBinary()).toFile()); + } + + @Test + @DisplayName("local pack binary exists but invalid, then throw exception") + void localPackCliCorrupt_thenThrowException() throws IOException { + // Given + givenPackCliPresentOnUserPath(String.format("/%s", getInvalidApplicablePackBinary())); + + // When + Then + assertThatIllegalStateException() + .isThrownBy(buildPackCliDownloader::getPackCLIIfPresentOrDownload) + .withMessage("No local pack binary found"); + } + + private void givenPackCliPresentOnUserPath(String packResource) throws IOException { + File bin = new File(temporaryFolder, "bin"); + File pack = new File(Objects.requireNonNull(getClass().getResource(packResource)).getFile()); + Files.createDirectory(bin.toPath()); + Files.copy(pack.toPath(), bin.toPath().resolve(pack.getName()), COPY_ATTRIBUTES); + } + } + } + + private Properties createPackProperties(TestHttpStaticServer server) { + Properties packProperties = new Properties(); + String baseUrl = String.format("http://localhost:%d/", server.getPort()); + packProperties.put("version", TEST_PACK_VERSION); + packProperties.put("linux.artifact", baseUrl + "pack-v" + TEST_PACK_VERSION + "-linux.tgz"); + packProperties.put("linux-arm64.artifact", baseUrl + "pack-v" + TEST_PACK_VERSION + "-linux-arm64.tgz"); + packProperties.put("macos.artifact", baseUrl + "pack-v" + TEST_PACK_VERSION + "-macos.tgz"); + packProperties.put("macos-arm64.artifact", baseUrl + "pack-v" + TEST_PACK_VERSION + "-macos-arm64.tgz"); + packProperties.put("windows.artifact", baseUrl + "pack-v" + TEST_PACK_VERSION + "-windows.zip"); + packProperties.put("windows.binary-extension", "bat"); + return packProperties; + } + + private void givenPackCliAlreadyDownloaded() throws IOException { + File jKubeDownloadDir = new File(temporaryFolder, ".jkube"); + Files.createDirectory(jKubeDownloadDir.toPath()); + Files.copy(oldPackCliInJKubeDir.toPath(), jKubeDownloadDir.toPath().resolve(oldPackCliInJKubeDir.getName()), COPY_ATTRIBUTES); + } +} diff --git a/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCommandTest.java b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCommandTest.java new file mode 100644 index 0000000000..e7e9b0db07 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/BuildPackCommandTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import org.eclipse.jkube.kit.common.KitLogger; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Collections; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; + +class BuildPackCommandTest { + private KitLogger kitLogger; + private File packCli; + + @BeforeEach + void setUp() { + kitLogger = new KitLogger.SilentLogger(); + packCli = new File(Objects.requireNonNull(getClass().getResource("/pack")).getFile()); + } + + @Test + void getArgs() { + // Given + BuildPackCommand buildPackCommand = new BuildPackCommand(kitLogger, packCli, Collections.singletonList("--version"), s -> {}); + + // When + Then + assertThat(buildPackCommand.getArgs()) + .containsExactly(packCli.getAbsolutePath(), "--version"); + } + + @Test + void processLine_whenInvoked_shouldSetVersion() { + // Given + AtomicReference version = new AtomicReference<>(); + BuildPackCommand buildPackCommand = new BuildPackCommand(kitLogger, packCli, Collections.singletonList("--version"), version::set); + // When + buildPackCommand.processLine("0.30.0"); + // Then + assertThat(version.get()).isEqualTo("0.30.0"); + } + + @Test + void processError_whenInvoked_shouldSetError() { + // Given + AtomicReference version = new AtomicReference<>(); + BuildPackCommand buildPackCommand = new BuildPackCommand(kitLogger, packCli, Collections.singletonList("--version"), version::set); + // When + buildPackCommand.processError("Failure in running pack Cli"); + // Then + assertThat(buildPackCommand.getError()).isEqualTo("Failure in running pack Cli"); + } +} diff --git a/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/LinuxArm64BuildPackCliDownloaderTest.java b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/LinuxArm64BuildPackCliDownloaderTest.java new file mode 100644 index 0000000000..54d2fc32ab --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/LinuxArm64BuildPackCliDownloaderTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +@EnabledOnOs({OS.LINUX, OS.MAC}) +public class LinuxArm64BuildPackCliDownloaderTest extends AbstractBuildPackCliDownloaderTest { + @Override + String getApplicablePackBinary() { + return "pack"; + } + + @Override + String getInvalidApplicablePackBinary() { + return "invalid-pack"; + } + + @Override + String getPlatform() { + return "Linux"; + } + + @Override + String getProcessorArchitecture() { + return "aarch64"; + } +} diff --git a/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/LinuxBuildPackCliDownloaderTest.java b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/LinuxBuildPackCliDownloaderTest.java new file mode 100644 index 0000000000..997270b933 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/LinuxBuildPackCliDownloaderTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + + +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +@EnabledOnOs({OS.LINUX, OS.MAC}) +class LinuxBuildPackCliDownloaderTest extends AbstractBuildPackCliDownloaderTest { + @Override + String getApplicablePackBinary() { + return "pack"; + } + + @Override + String getInvalidApplicablePackBinary() { + return "invalid-pack"; + } + + @Override + String getPlatform() { + return "Linux"; + } + + @Override + String getProcessorArchitecture() { + return "amd64"; + } +} diff --git a/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/MacOsArm64BuildPackCliDownloaderTest.java b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/MacOsArm64BuildPackCliDownloaderTest.java new file mode 100644 index 0000000000..82be11baa8 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/MacOsArm64BuildPackCliDownloaderTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +@EnabledOnOs({OS.LINUX, OS.MAC}) +public class MacOsArm64BuildPackCliDownloaderTest extends AbstractBuildPackCliDownloaderTest { + @Override + String getApplicablePackBinary() { + return "pack"; + } + + @Override + String getInvalidApplicablePackBinary() { + return "invalid-pack"; + } + + @Override + String getPlatform() { + return "Mac OS X"; + } + + @Override + String getProcessorArchitecture() { + return "aarch64"; + } +} diff --git a/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/MacOsBuildPackCliDownloaderTest.java b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/MacOsBuildPackCliDownloaderTest.java new file mode 100644 index 0000000000..df89086732 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/MacOsBuildPackCliDownloaderTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +@EnabledOnOs({OS.LINUX, OS.MAC}) +class MacOsBuildPackCliDownloaderTest extends AbstractBuildPackCliDownloaderTest { + @Override + String getApplicablePackBinary() { + return "pack"; + } + + @Override + String getInvalidApplicablePackBinary() { + return "invalid-pack"; + } + + @Override + String getPlatform() { + return "Mac OS X"; + } + + @Override + String getProcessorArchitecture() { + return "amd64"; + } +} diff --git a/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/WindowsBuildPackCliDownloaderTest.java b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/WindowsBuildPackCliDownloaderTest.java new file mode 100644 index 0000000000..0906c0195f --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/java/org/eclipse/jkube/kit/service/buildpacks/WindowsBuildPackCliDownloaderTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2019 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at: + * + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.jkube.kit.service.buildpacks; + +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +@EnabledOnOs(OS.WINDOWS) +class WindowsBuildPackCliDownloaderTest extends AbstractBuildPackCliDownloaderTest { + @Override + String getApplicablePackBinary() { + return "pack.bat"; + } + + @Override + String getInvalidApplicablePackBinary() { + return "invalid-pack.bat"; + } + + @Override + String getPlatform() { + return "Windows 11"; + } + + @Override + String getProcessorArchitecture() { + return "amd64"; + } +} diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-linux-arm64.tgz b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-linux-arm64.tgz new file mode 100644 index 0000000000..85256e62fb Binary files /dev/null and b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-linux-arm64.tgz differ diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-linux.tgz b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-linux.tgz new file mode 100644 index 0000000000..85256e62fb Binary files /dev/null and b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-linux.tgz differ diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-macos-arm64.tgz b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-macos-arm64.tgz new file mode 100644 index 0000000000..85256e62fb Binary files /dev/null and b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-macos-arm64.tgz differ diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-macos.tgz b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-macos.tgz new file mode 100644 index 0000000000..85256e62fb Binary files /dev/null and b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-macos.tgz differ diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-windows.zip b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-windows.zip new file mode 100644 index 0000000000..e4603d2d4c Binary files /dev/null and b/jkube-kit/build/service/buildpacks/src/test/resources/artifacts/pack-v0.32.1-windows.zip differ diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/invalid-artifacts/foo.tgz b/jkube-kit/build/service/buildpacks/src/test/resources/invalid-artifacts/foo.tgz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/invalid-pack b/jkube-kit/build/service/buildpacks/src/test/resources/invalid-pack new file mode 100755 index 0000000000..6dedc57766 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/resources/invalid-pack @@ -0,0 +1 @@ +exit 1 \ No newline at end of file diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/invalid-pack.bat b/jkube-kit/build/service/buildpacks/src/test/resources/invalid-pack.bat new file mode 100755 index 0000000000..6c47b8292f --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/resources/invalid-pack.bat @@ -0,0 +1,3 @@ +@echo off + +EXIT /b 1 \ No newline at end of file diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/pack b/jkube-kit/build/service/buildpacks/src/test/resources/pack new file mode 100755 index 0000000000..9dc0481395 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/resources/pack @@ -0,0 +1,10 @@ +#!/bin/bash +printVersion () { + echo "0.32.1+git-b14250b.build-5241" +} + +if [ "$1" = "--version" ]; then + printVersion +else + echo "$@" +fi \ No newline at end of file diff --git a/jkube-kit/build/service/buildpacks/src/test/resources/pack.bat b/jkube-kit/build/service/buildpacks/src/test/resources/pack.bat new file mode 100755 index 0000000000..16ccba3584 --- /dev/null +++ b/jkube-kit/build/service/buildpacks/src/test/resources/pack.bat @@ -0,0 +1,9 @@ +@echo off + +echo %* +IF "%~1" == "--version" ( + echo 0.32.1+git-b14250b.build-5241 +) ELSE ( + echo "%*" +) +EXIT /B 0 \ No newline at end of file diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/EnvUtil.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/EnvUtil.java index d31300e470..7939186a34 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/EnvUtil.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/EnvUtil.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Properties; import java.util.TreeMap; import java.util.concurrent.TimeUnit; @@ -61,6 +62,7 @@ public class EnvUtil { private static final String COMMA_WHITESPACE = COMMA + WHITESPACE; private static UnaryOperator envGetter = System::getenv; + private static UnaryOperator propertyGetter = System::getProperty; private EnvUtil() { } @@ -72,6 +74,11 @@ private EnvUtil() { public static void overrideEnvGetter(UnaryOperator getter) { envGetter = getter; } + + public static void overridePropertyGetter(UnaryOperator propsGetter) { + propertyGetter = propsGetter; + } + // Convert docker host URL to an HTTP(s) URL public static String convertTcpToHttpUrl(String connect) { String protocol = connect.contains(":" + DOCKER_HTTP_PORT) ? "http:" : "https:"; @@ -453,7 +460,7 @@ public static Date loadTimestamp(File tsFile) throws IOException { } public static boolean isWindows() { - return System.getProperty("os.name").toLowerCase().contains("windows"); + return getProperty("os.name").toLowerCase().contains("windows"); } /** @@ -461,7 +468,7 @@ public static boolean isWindows() { * @return the user home directory. */ public static File getUserHome() { - String homeDir = System.getProperty("user.home"); + String homeDir = getProperty("user.home"); if (homeDir == null) { homeDir = getEnv("HOME"); } @@ -477,6 +484,10 @@ public static String getEnv(String variableName) { return envGetter.apply(variableName); } + public static String getProperty(String propertyName) { + return propertyGetter.apply(propertyName); + } + public static String getEnvVarOrSystemProperty(String varName, String defaultValue) { return getEnvVarOrSystemProperty(varName, varName, defaultValue); } @@ -486,9 +497,41 @@ public static String getEnvVarOrSystemProperty(String envVarName, String systemP if (StringUtils.isNotBlank(ret)){ return ret; } - return System.getProperty(systemProperty, defaultValue); + return Optional.ofNullable(getProperty(systemProperty)).orElse(defaultValue); } -} + /** + * Utility method to get underlying processor architecture + * @return String value containing architecture + */ + public static String getProcessorArchitecture() { + return getProperty("os.arch"); + } + /** + * Is underlying Operating System from Mac OS family. + * + * @return boolean value indicating whether Operating System is Mac or not. + */ + public static boolean isMacOs() { + return getProperty("os.name").toLowerCase().contains("mac"); + } + /** + * Find a binary in user's path + * + * @param binaryName name of file to find + * @return {@link File} containing binary's path, otherwise null. + */ + public static File findBinaryFileInUserPath(String binaryName) { + String userPath = getEnv("PATH"); + if (userPath != null) { + return Arrays.stream(userPath.split(File.pathSeparator)) + .map(p -> new File(p, binaryName)) + .filter(f -> f.isFile() && f.exists()) + .findFirst() + .orElse(null); + } + return null; + } +} \ No newline at end of file diff --git a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtil.java b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtil.java index 1d0df75864..4baf9a9787 100644 --- a/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtil.java +++ b/jkube-kit/common/src/main/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtil.java @@ -31,6 +31,20 @@ public static boolean isVersionAtLeast(int majorVersion, int minorVersion, Strin return false; } + /** + * Remove build metadata from provided version + * + * @param version version with full version+(build metadata) format + * @return string containing just the version + */ + public static String removeBuildMetadata(String version) { + if (StringUtils.isNotBlank(version) && version.contains("+")) { + int indexOfBuildMetadataDelimiter = version.indexOf('+'); + return version.substring(0, indexOfBuildMetadataDelimiter); + } + return version; + } + private static int parseInt(String toParse) { try { return Integer.parseInt(toParse); diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/EnvUtilTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/EnvUtilTest.java index 976d6c2f06..547a7608be 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/EnvUtilTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/EnvUtilTest.java @@ -15,6 +15,7 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collections; import java.util.Date; @@ -33,6 +34,7 @@ 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.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import static org.assertj.core.api.Assertions.assertThat; @@ -418,4 +420,74 @@ public static Stream greaterOrEqualVersionTestData(){ Arguments.arguments("Greater or Equal version when false", "3.1.1.0", "4.0.2", false) ); } + + @ParameterizedTest(name = "when os.arch = {0}, then return {1}") + @CsvSource({ + "aarch64,aarch64", + "amd64,amd64", + "x86,x86" + }) + void isProcessorArchitectureARM_whenSystemPropertyProvided_thenReturnExpectedResult(String osArchPropertyValue, String result) { + try { + // Given + Map propMap = Collections.singletonMap("os.arch", osArchPropertyValue); + EnvUtil.overridePropertyGetter(propMap::get); + + // When + Then + assertThat(EnvUtil.getProcessorArchitecture()).isEqualTo(result); + } finally { + EnvUtil.overridePropertyGetter(System::getProperty); + } + } + + @ParameterizedTest(name = "when os.name = {0}, then return {1}") + @CsvSource({ + "Mac OS X,true", + "Windows 8.1,false", + "Linux,false" + }) + void isMacOs_whenSystemPropertyProvided_thenReturnExpectedResult(String osArchPropertyValue, boolean result) { + try { + // Given + Map propMap = Collections.singletonMap("os.name", osArchPropertyValue); + EnvUtil.overridePropertyGetter(propMap::get); + + // When + Then + assertThat(EnvUtil.isMacOs()).isEqualTo(result); + } finally { + EnvUtil.overridePropertyGetter(System::getProperty); + } + } + + @Test + @EnabledOnOs({OS.LINUX,OS.MAC}) + void findBinaryFileInUserPath_whenFilePresentInPath_thenReturnFile(@TempDir File temporaryFolder) throws IOException { + try { + // Given + File binaryFile = new File(temporaryFolder, "foo"); + Files.createFile(binaryFile.toPath()); + Map envMap = Collections.singletonMap("PATH", "/usr/local/bin" + File.pathSeparator + temporaryFolder.getAbsolutePath()); + EnvUtil.overrideEnvGetter(envMap::get); + + // When + Then + assertThat(EnvUtil.findBinaryFileInUserPath("foo")).isEqualTo(binaryFile); + } finally { + EnvUtil.overrideEnvGetter(System::getenv); + } + } + + @Test + @EnabledOnOs({OS.LINUX,OS.MAC}) + void findBinaryFileInUserPath_whenFileNotFound_thenReturnNull(@TempDir File temporaryFolder) { + try { + // Given + Map envMap = Collections.singletonMap("PATH", "/usr/local/bin" + File.pathSeparator + temporaryFolder.getAbsolutePath()); + EnvUtil.overrideEnvGetter(envMap::get); + + // When + Then + assertThat(EnvUtil.findBinaryFileInUserPath("foo")).isNull(); + } finally { + EnvUtil.overrideEnvGetter(System::getenv); + } + } } diff --git a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtilTest.java b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtilTest.java index 0fefb010f8..3f5c056048 100644 --- a/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtilTest.java +++ b/jkube-kit/common/src/test/java/org/eclipse/jkube/kit/common/util/SemanticVersionUtilTest.java @@ -16,6 +16,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.MethodSource; import java.util.stream.Stream; @@ -39,8 +40,18 @@ public static Stream versionTestData() { Arguments.arguments("With same major and minor version should return true", 1, 13, "1.13.7.Final", true), Arguments.arguments("With same major and smaller minor version should return true", 1, 12, "1.13.7.Final", true), Arguments.arguments("With smaller major and larger minor version should return true", 0, 12, "1.13.7.Final", true), - Arguments.arguments("With smaller major and incomplete version should return true", 0, 12, "1.Final", true) + Arguments.arguments("With smaller major and incomplete version should return true", 0, 12, "1.Final", true), + Arguments.arguments("With invalid version should return false", 2, 1, "two.one.seven.Final", false) ); } + @ParameterizedTest(name = "given {0} should return {1} without metadata") + @CsvSource({ + "0.31.0+git-3a994bd.build-5086,0.31.0", + "24.0.7,24.0.7" + }) + void removeBuildMetadata_whenSemanticVersionProvided_thenTrimMetadata(String semanticVersion, String expectedVersionWithoutMetadata) { + assertThat(SemanticVersionUtil.removeBuildMetadata(semanticVersion)).isEqualTo(expectedVersionWithoutMetadata); + } + } diff --git a/jkube-kit/parent/pom.xml b/jkube-kit/parent/pom.xml index f244a0f5bb..a6058066a5 100644 --- a/jkube-kit/parent/pom.xml +++ b/jkube-kit/parent/pom.xml @@ -127,6 +127,14 @@ wildfly:${version.image.wildfly.upstream.s2i} 0.24.0 + + 0.32.1 + exe + https://github.com/buildpacks/pack/releases/download/v${pack.version}/pack-v${pack.version}-linux.tgz + https://github.com/buildpacks/pack/releases/download/v${pack.version}/pack-v${pack.version}-linux-arm64.tgz + https://github.com/buildpacks/pack/releases/download/v${pack.version}/pack-v${pack.version}-macos.tgz + https://github.com/buildpacks/pack/releases/download/v${pack.version}/pack-v${pack.version}-macos-arm64.tgz + https://github.com/buildpacks/pack/releases/download/v${pack.version}/pack-v${pack.version}-windows.zip