From a109c78a1ce13549997ff50b489e7fed2916acef Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Wed, 6 Dec 2023 20:51:45 +0530 Subject: [PATCH] feat (jkube-kit/config/service) : Add BuildPackBuildService (#2493) + Add new enum for buildpacks in JKubeBuildStrategy + Add BuildPackBuildService that would be activated when build strategy is set to buildpacks Signed-off-by: Rohan Kumar --- .../buildpacks/BuildPackCliDownloader.java | 5 + .../AbstractBuildPackCliDownloaderTest.java | 2 +- .../image/build/JKubeBuildStrategy.java | 7 +- jkube-kit/config/service/pom.xml | 4 + .../kubernetes/BuildPackBuildService.java | 119 +++++++++++ .../resources/META-INF/jkube/build-service | 1 + .../JKubeServiceHubBuildServiceTest.java | 2 + .../kubernetes/BuildPackBuildServiceTest.java | 189 ++++++++++++++++++ jkube-kit/parent/pom.xml | 13 ++ jkube-kit/pom.xml | 2 +- 10 files changed, 341 insertions(+), 3 deletions(-) create mode 100644 jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildService.java create mode 100644 jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildServiceTest.java 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 index 44ae9054ae..51d553366f 100644 --- 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 @@ -16,6 +16,7 @@ import org.apache.commons.lang3.StringUtils; import org.eclipse.jkube.kit.common.KitLogger; import org.eclipse.jkube.kit.common.util.FileUtil; +import org.eclipse.jkube.kit.common.util.PropertiesUtil; import java.io.File; import java.io.IOException; @@ -51,6 +52,10 @@ public class BuildPackCliDownloader { private final Properties packProperties; private final File jKubeUserHomeDir; + public BuildPackCliDownloader(KitLogger kitLogger) { + this(kitLogger, PropertiesUtil.getPropertiesFromResource(BuildPackCliDownloader.class.getResource("/META-INF/jkube/pack-cli.properties"))); + } + public BuildPackCliDownloader(KitLogger kitLogger, Properties packProperties) { this.kitLogger = kitLogger; this.packProperties = packProperties; 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 index 84e2ccafe9..3d29742861 100644 --- 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 @@ -55,7 +55,7 @@ abstract class AbstractBuildPackCliDownloaderTest { abstract String getProcessorArchitecture(); @BeforeEach - void setUp() throws IOException { + void setUp() { kitLogger = spy(new KitLogger.SilentLogger()); Map overriddenSystemProperties = new HashMap<>(); Map overriddenEnvironmentVariables = new HashMap<>(); 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..8c675da014 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 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..a2e6c253eb --- /dev/null +++ b/jkube-kit/config/service/src/main/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildService.java @@ -0,0 +1,119 @@ +/* + * 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 BuildPackCliDownloader buildPackCliDownloader; + + public BuildPackBuildService(JKubeServiceHub jKubeServiceHub) { + this(jKubeServiceHub, null); + } + + BuildPackBuildService(JKubeServiceHub jKubeServiceHub, Properties packProperties) { + super(jKubeServiceHub); + this.buildServiceConfig = Objects.requireNonNull(jKubeServiceHub.getBuildServiceConfig(), + "BuildServiceConfig is required"); + this.kitLogger = Objects.requireNonNull(jKubeServiceHub.getLog()); + if (packProperties == null) { + this.buildPackCliDownloader = new BuildPackCliDownloader(kitLogger); + } else { + this.buildPackCliDownloader = new BuildPackCliDownloader(kitLogger, packProperties); + } + } + + @Override + protected void buildSingleImage(ImageConfiguration imageConfiguration) { + kitLogger.info("Delegating container image building process to BuildPacks"); + File packCli = buildPackCliDownloader.getPackCLIIfPresentOrDownload(); + kitLogger.info("Using pack %s", packCli.getAbsolutePath()); + BuildPackCliController packCliController = new BuildPackCliController(packCli, kitLogger); + + packCliController.build(BuildPackBuildOptions.builder() + .imageName(imageConfiguration.getName()) + .builderImage(resolveBuildPackBuilderImage()) + .creationTime("now") + .build()); + } + + private String resolveBuildPackBuilderImage() { + File localPackConfig = resolveLocalPackConfig(); + + if (localPackConfig != null && localPackConfig.exists()) { + Properties localPackConfigAsProperties = readLocalPackConfig(localPackConfig); + if (localPackConfigAsProperties.get("default-builder-image") != null) { + return strip(localPackConfigAsProperties.getProperty("default-builder-image"), "\""); + } + } + return DEFAULT_BUILDER_IMAGE; + } + + private Properties readLocalPackConfig(File packConfig) { + try { + return PropertiesUtil.getPropertiesFromResource(packConfig.toURI().toURL()); + } catch (MalformedURLException e) { + kitLogger.warn("Failure in reading pack local configuration : " + e.getMessage()); + } + return new Properties(); + } + + private File resolveLocalPackConfig() { + File packConfigDir = new File(EnvUtil.getUserHome(), PACK_CONFIG_DIR); + if (packConfigDir.exists() && packConfigDir.isDirectory()) { + return new File(packConfigDir, PACK_CONFIG_FILE); + } + return null; + } + + @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..8e92d0048f --- /dev/null +++ b/jkube-kit/config/service/src/test/java/org/eclipse/jkube/kit/config/service/kubernetes/BuildPackBuildServiceTest.java @@ -0,0 +1,189 @@ +/* + * 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.TestHttpBuildPacksArtifactsServer; +import org.eclipse.jkube.kit.common.util.EnvUtil; +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.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 TestHttpBuildPacksArtifactsServer server; + private BuildPackBuildService buildPackBuildService; + + @BeforeEach + void setUp() { + server = new TestHttpBuildPacksArtifactsServer(); + Properties packProperties = new Properties(); + packProperties.put("version", TEST_PACK_VERSION); + packProperties.put("windows.binary-extension", "bat"); + buildPackBuildService = new BuildPackBuildService(jKubeServiceHub, packProperties); + packProperties.put("linux.artifact", server.getLinuxArtifactUrl()); + packProperties.put("linux-arm64.artifact", server.getLinuxArm64ArtifactUrl()); + packProperties.put("macos.artifact", server.getMacosArtifactUrl()); + packProperties.put("macos-arm64.artifact", server.getMacosArm64ArtifactUrl()); + packProperties.put("windows.artifact", server.getWindowsArtifactUrl()); + packProperties.put("windows.binary-extension", "bat"); + } + + @AfterEach + void tearDown() throws IOException { + server.close(); + } + + @Nested + @DisplayName("local .pack/config.toml exists") + class LocalPackConfigExists { + private File localPackConfig; + + @BeforeEach + void setUp() throws IOException { + File packHome = new File(temporaryFolder, ".pack"); + Files.createDirectory(packHome.toPath()); + localPackConfig = new File(packHome, "config.toml"); + } + + @Test + @DisplayName("When default builder configured in .pack/config.toml, then use that builder image") + void whenLocalPackConfigHasDefaultBuilderSet_thenUseThatBuilder() throws IOException { + // Given + Files.write(localPackConfig.toPath(), String.format("default-builder-image=\"%s\"", "cnbs/sample-builder:bionic").getBytes()); + + // 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 + Files.write(localPackConfig.toPath(), "default-builder-image@@=".getBytes()); + + // When + buildPackBuildService.buildSingleImage(imageConfiguration); + + // Then + verify(kitLogger).info("[[s]]%s","build foo/bar:latest --builder paketobuildpacks/builder:base --creation-time now"); + } + } + + @Nested + @DisplayName("Local .pack/config.toml absent") + class LocalPackConfigAbsent { + @Test + @DisplayName("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"); + } + } + } +} diff --git a/jkube-kit/parent/pom.xml b/jkube-kit/parent/pom.xml index a760afcbbf..4c45b2708a 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