diff --git a/jkube-kit/build/service/buildpacks/pom.xml b/jkube-kit/build/service/buildpacks/pom.xml index 6bf5c3b47b..31f906ded3 100644 --- a/jkube-kit/build/service/buildpacks/pom.xml +++ b/jkube-kit/build/service/buildpacks/pom.xml @@ -74,6 +74,18 @@ false + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + - diff --git a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/JKubeBuildStrategy.java b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/JKubeBuildStrategy.java index 9bacd48741..5e2eee6e45 100644 --- a/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/JKubeBuildStrategy.java +++ b/jkube-kit/config/image/src/main/java/org/eclipse/jkube/kit/config/image/build/JKubeBuildStrategy.java @@ -35,7 +35,12 @@ public enum JKubeBuildStrategy { /** * Docker build with a binary source */ - docker("Docker"); + docker("Docker"), + + /** + * BuildPacks + */ + buildpacks("Buildpacks"); // Source strategy elements public enum SourceStrategy { diff --git a/jkube-kit/config/service/pom.xml b/jkube-kit/config/service/pom.xml index 7a52f9ccc5..94e8a5dc6e 100644 --- a/jkube-kit/config/service/pom.xml +++ b/jkube-kit/config/service/pom.xml @@ -41,6 +41,10 @@ org.eclipse.jkube jkube-kit-build-service-jib + + org.eclipse.jkube + jkube-kit-build-service-buildpacks + org.eclipse.jkube jkube-kit-helm @@ -56,6 +60,12 @@ test test-jar + + org.eclipse.jkube + jkube-kit-build-service-buildpacks + test-jar + test + org.junit.jupiter junit-jupiter-engine diff --git a/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildService.java b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildService.java new file mode 100644 index 0000000000..a9164d664b --- /dev/null +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildService.java @@ -0,0 +1,111 @@ +/* + * 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.config.service.kubernetes; + +import java.io.File; +import java.net.MalformedURLException; +import java.util.Objects; +import java.util.Properties; + +import org.eclipse.jkube.kit.common.KitLogger; +import org.eclipse.jkube.kit.common.RegistryConfig; +import org.eclipse.jkube.kit.common.util.EnvUtil; +import org.eclipse.jkube.kit.common.util.PropertiesUtil; +import org.eclipse.jkube.kit.config.image.ImageConfiguration; +import org.eclipse.jkube.kit.config.image.build.JKubeBuildStrategy; +import org.eclipse.jkube.kit.config.service.AbstractImageBuildService; +import org.eclipse.jkube.kit.config.service.BuildServiceConfig; +import org.eclipse.jkube.kit.config.service.JKubeServiceException; +import org.eclipse.jkube.kit.config.service.JKubeServiceHub; +import org.eclipse.jkube.kit.service.buildpacks.BuildPackBuildOptions; +import org.eclipse.jkube.kit.service.buildpacks.BuildPackCliDownloader; +import org.eclipse.jkube.kit.service.buildpacks.controller.BuildPackCliController; + +import static org.apache.commons.lang3.StringUtils.strip; + +public class BuildPackBuildService extends AbstractImageBuildService { + private static final String DEFAULT_BUILDER_IMAGE = "paketobuildpacks/builder:base"; + private static final String PACK_CONFIG_DIR = ".pack"; + private static final String PACK_CONFIG_FILE = "config.toml"; + + private final BuildServiceConfig buildServiceConfig; + private final KitLogger kitLogger; + private final Properties packProperties; + + public BuildPackBuildService(JKubeServiceHub jKubeServiceHub) { + this(jKubeServiceHub, PropertiesUtil.getPropertiesFromResource(BuildPackBuildService.class.getResource("/META-INF/jkube/pack-cli.properties"))); + } + + BuildPackBuildService(JKubeServiceHub jKubeServiceHub, Properties packProperties) { + super(jKubeServiceHub); + this.buildServiceConfig = Objects.requireNonNull(jKubeServiceHub.getBuildServiceConfig(), + "BuildServiceConfig is required"); + this.kitLogger = Objects.requireNonNull(jKubeServiceHub.getLog()); + this.packProperties = packProperties; + + } + + @Override + protected void buildSingleImage(ImageConfiguration imageConfiguration) { + kitLogger.info("Delegating container image building process to BuildPacks"); + doBuildPackBuild(imageConfiguration); + } + + private void doBuildPackBuild(ImageConfiguration imageConfiguration) { + BuildPackCliDownloader packCliDownloader = new BuildPackCliDownloader(kitLogger, packProperties); + File packCli = packCliDownloader.getPackCLIIfPresentOrDownload(); + kitLogger.info("Using pack %s", packCli.getAbsolutePath()); + BuildPackCliController packCliController = new BuildPackCliController(packCli, kitLogger); + BuildPackBuildOptions buildOptions = createBuildPackOptions(imageConfiguration); + packCliController.build(buildOptions); + } + + private BuildPackBuildOptions createBuildPackOptions(ImageConfiguration imageConfiguration) { + Properties packConfigProperties = readLocalPackConfig(); + String builderImage = strip(packConfigProperties.getProperty("default-builder-image", DEFAULT_BUILDER_IMAGE), "\""); + BuildPackBuildOptions.BuildPackBuildOptionsBuilder buildOptionsBuilder = BuildPackBuildOptions.builder(); + buildOptionsBuilder.imageName(imageConfiguration.getName()); + buildOptionsBuilder.builderImage(builderImage); + buildOptionsBuilder.creationTime("now"); + return buildOptionsBuilder.build(); + } + + private Properties readLocalPackConfig() { + File packConfigDir = new File(EnvUtil.getUserHome(), PACK_CONFIG_DIR); + if (packConfigDir.exists() && packConfigDir.isDirectory()) { + File packConfig = new File(packConfigDir, PACK_CONFIG_FILE); + try { + return PropertiesUtil.getPropertiesFromResource(packConfig.toURI().toURL()); + } catch (MalformedURLException e) { + kitLogger.warn("Failure in reading pack local configuration : " + e.getMessage()); + } + } + return new Properties(); + } + + @Override + protected void pushSingleImage(ImageConfiguration imageConfiguration, int retries, RegistryConfig registryConfig, boolean skipTag) throws JKubeServiceException { + } + + @Override + public boolean isApplicable() { + return buildServiceConfig.getJKubeBuildStrategy() != null && + buildServiceConfig.getJKubeBuildStrategy().equals(JKubeBuildStrategy.buildpacks); + } + + @Override + public void postProcess() { + // NOOP + } +} diff --git a/jkube-kit/config/service/src/main/resources/META-INF/jkube/build-service b/jkube-kit/config/service/src/main/resources/META-INF/jkube/build-service index 2922021448..cb5f809098 100644 --- a/jkube-kit/config/service/src/main/resources/META-INF/jkube/build-service +++ b/jkube-kit/config/service/src/main/resources/META-INF/jkube/build-service @@ -1,3 +1,4 @@ org.eclipse.jkube.kit.config.service.kubernetes.JibBuildService,100 +org.eclipse.jkube.kit.config.service.kubernetes.BuildPackBuildService,110 org.eclipse.jkube.kit.config.service.openshift.OpenshiftBuildService,200 org.eclipse.jkube.kit.config.service.kubernetes.DockerBuildService,9999 \ No newline at end of file diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/JKubeServiceHubBuildServiceTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/JKubeServiceHubBuildServiceTest.java index 0472df2b47..9df6805012 100644 --- a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/JKubeServiceHubBuildServiceTest.java +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/JKubeServiceHubBuildServiceTest.java @@ -21,6 +21,7 @@ import org.eclipse.jkube.kit.common.util.LazyBuilder; import org.eclipse.jkube.kit.config.image.build.JKubeBuildStrategy; import org.eclipse.jkube.kit.config.resource.RuntimeMode; +import org.eclipse.jkube.kit.config.service.kubernetes.BuildPackBuildService; import org.eclipse.jkube.kit.config.service.kubernetes.DockerBuildService; import org.eclipse.jkube.kit.config.service.kubernetes.JibBuildService; import org.eclipse.jkube.kit.config.service.openshift.OpenshiftBuildService; @@ -42,6 +43,7 @@ static Stream data() { arguments(RuntimeMode.KUBERNETES, JKubeBuildStrategy.docker, DockerBuildService.class), arguments(RuntimeMode.KUBERNETES, JKubeBuildStrategy.s2i, DockerBuildService.class), arguments(RuntimeMode.KUBERNETES, JKubeBuildStrategy.jib, JibBuildService.class), + arguments(RuntimeMode.KUBERNETES, JKubeBuildStrategy.buildpacks, BuildPackBuildService.class), arguments(RuntimeMode.OPENSHIFT, null, OpenshiftBuildService.class), arguments(RuntimeMode.OPENSHIFT, JKubeBuildStrategy.docker, OpenshiftBuildService.class), arguments(RuntimeMode.OPENSHIFT, JKubeBuildStrategy.s2i, OpenshiftBuildService.class), diff --git a/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildServiceTest.java b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildServiceTest.java new file mode 100644 index 0000000000..effbac31db --- /dev/null +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildServiceTest.java @@ -0,0 +1,183 @@ +/* + * 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.config.service.kubernetes; + +import org.eclipse.jkube.kit.common.JKubeConfiguration; +import org.eclipse.jkube.kit.common.KitLogger; +import org.eclipse.jkube.kit.common.TestHttpStaticServer; +import org.eclipse.jkube.kit.common.util.EnvUtil; +import org.eclipse.jkube.kit.common.util.FileUtil; +import org.eclipse.jkube.kit.config.image.ImageConfiguration; +import org.eclipse.jkube.kit.config.image.build.BuildConfiguration; +import org.eclipse.jkube.kit.config.image.build.JKubeBuildStrategy; +import org.eclipse.jkube.kit.config.resource.RuntimeMode; +import org.eclipse.jkube.kit.config.service.BuildServiceConfig; +import org.eclipse.jkube.kit.config.service.JKubeServiceHub; + +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.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +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 static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +class BuildPackBuildServiceTest { + private KitLogger kitLogger; + private JKubeServiceHub jKubeServiceHub; + private static final String TEST_PACK_VERSION = "v0.32.1"; + private ImageConfiguration imageConfiguration; + private BuildServiceConfig buildServiceConfig; + + @TempDir + private File temporaryFolder; + + @BeforeEach + void setUp() { + kitLogger = spy(new KitLogger.SilentLogger()); + buildServiceConfig = BuildServiceConfig.builder() + .jKubeBuildStrategy(JKubeBuildStrategy.buildpacks) + .build(); + jKubeServiceHub = JKubeServiceHub.builder() + .log(kitLogger) + .platformMode(RuntimeMode.KUBERNETES) + .buildServiceConfig(buildServiceConfig) + .configuration(JKubeConfiguration.builder().build()) + .build(); + imageConfiguration = ImageConfiguration.builder() + .name("foo/bar:latest") + .build(BuildConfiguration.builder() + .from("foo/base:latest") + .build()) + .build(); + Map properties = new HashMap<>(); + properties.put("user.home", temporaryFolder.getAbsolutePath()); + properties.put("os.name", System.getProperty("os.name")); + properties.put("os.arch", System.getProperty("os.arch")); + Map env = new HashMap<>(); + env.put("HOME", temporaryFolder.getAbsolutePath()); + env.put("PATH", temporaryFolder.toPath().resolve("bin").toFile().getAbsolutePath()); + EnvUtil.overrideEnvGetter(env::get); + EnvUtil.overridePropertyGetter(properties::get); + } + + @AfterEach + void tearDown() { + EnvUtil.overrideEnvGetter(System::getenv); + EnvUtil.overridePropertyGetter(System::getProperty); + } + + @ParameterizedTest + @CsvSource({ + "s2i,false", "jib,false", "docker,false", "buildpacks,true" + }) + void isApplicable_withGivenStrategy_shouldReturnTrueOnlyForBuildPackStrategy(String buildStrategyValue, boolean expectedResult) { + // Given + jKubeServiceHub = jKubeServiceHub.toBuilder() + .buildServiceConfig(buildServiceConfig.toBuilder() + .jKubeBuildStrategy(JKubeBuildStrategy.valueOf(buildStrategyValue)) + .build()) + .build(); + // When + final boolean result = new BuildPackBuildService(jKubeServiceHub).isApplicable(); + // Then + assertThat(result).isEqualTo(expectedResult); + } + + + @Nested + @DisplayName("buildImage") + class BuildImage { + private TestHttpStaticServer server; + private BuildPackBuildService buildPackBuildService; + + @BeforeEach + void setUp() throws IOException { + File remoteDirectory = new File(Objects.requireNonNull(getClass().getResource("/artifacts")).getFile()); + File downloadArtifactsDir = temporaryFolder.toPath().resolve("download-artifacts").toFile(); + FileUtil.copyDirectoryIfNotExists(remoteDirectory, downloadArtifactsDir); + server = new TestHttpStaticServer(downloadArtifactsDir); + Properties properties = new Properties(); + properties.put("version", TEST_PACK_VERSION); + properties.put("windows.binary-extension", "bat"); + properties.put("linux.artifact", String.format("http://localhost:%d/pack-%s-linux.tgz", server.getPort(), TEST_PACK_VERSION)); + properties.put("linux-arm64.artifact", String.format("http://localhost:%d/pack-%s-linux-arm64.tgz", server.getPort(), TEST_PACK_VERSION)); + properties.put("macos.artifact", String.format("http://localhost:%d/pack-%s-macos.tgz", server.getPort(), TEST_PACK_VERSION)); + properties.put("macos-arm64.artifact", String.format("http://localhost:%d/pack-%s-macos-arm64.tgz", server.getPort(), TEST_PACK_VERSION)); + properties.put("windows.artifact", String.format("http://localhost:%d/pack-%s-windows.zip", server.getPort(), TEST_PACK_VERSION)); + buildPackBuildService = new BuildPackBuildService(jKubeServiceHub, properties); + } + + @AfterEach + void tearDown() throws IOException { + server.close(); + } + + @Test + @DisplayName("When default builder configured in .pack/config.toml, then use that builder image") + void whenLocalPackConfigHasDefaultBuilderSet_thenUseThatBuilder() throws IOException { + // Given + givenLocalPackConfigExistsWithContent(temporaryFolder, String.format("default-builder-image=\"%s\"", "cnbs/sample-builder:bionic")); + + // When + buildPackBuildService.buildSingleImage(imageConfiguration); + + // Then + verify(kitLogger).info("[[s]]%s","build foo/bar:latest --builder cnbs/sample-builder:bionic --creation-time now"); + } + + @Test + @DisplayName("When .pack/config.toml invalid, then use opinionated builder image") + void whenLocalPackConfigInvalid_thenUseOpinionatedBuilderImage() throws IOException { + // Given + givenLocalPackConfigExistsWithContent(temporaryFolder, "default-builder-image@@="); + + // When + buildPackBuildService.buildSingleImage(imageConfiguration); + + // Then + verify(kitLogger).info("[[s]]%s","build foo/bar:latest --builder paketobuildpacks/builder:base --creation-time now"); + } + + @Test + @DisplayName("When no default builder configured in .pack/config.toml, then use opinionated builder image") + void whenLocalPackCLIAndNoDefaultBuilderInPackConfig_thenUseOpinionatedBuilderImage() { + // When + buildPackBuildService.buildSingleImage(imageConfiguration); + + // Then + verify(kitLogger).info("[[s]]%s", "build foo/bar:latest --builder paketobuildpacks/builder:base --creation-time now"); + } + } + + private void givenLocalPackConfigExistsWithContent(File userHome, String content) throws IOException { + File packHome = new File(userHome, ".pack"); + Files.createDirectory(packHome.toPath()); + File packConfig = new File(packHome, "config.toml"); + Files.write(packConfig.toPath(), content.getBytes()); + } +} diff --git a/jkube-kit/parent/pom.xml b/jkube-kit/parent/pom.xml index 74815950f6..6e337abd7d 100644 --- a/jkube-kit/parent/pom.xml +++ b/jkube-kit/parent/pom.xml @@ -159,6 +159,13 @@ ${project.version} test-jar + + org.eclipse.jkube + jkube-kit-build-service-buildpacks + ${project.version} + test + test-jar + org.eclipse.jkube @@ -215,6 +222,12 @@ ${project.version} + + org.eclipse.jkube + jkube-kit-build-service-buildpacks + ${project.version} + + org.eclipse.jkube jkube-kit-watcher-api diff --git a/jkube-kit/pom.xml b/jkube-kit/pom.xml index ec4bfc835f..5143bf4faa 100644 --- a/jkube-kit/pom.xml +++ b/jkube-kit/pom.xml @@ -38,11 +38,11 @@ common-maven config/image config/resource - config/service build/api build/service/buildpacks build/service/docker build/service/jib + config/service generator/api generator/java-exec generator/karaf