From e365794d4c65def694e4c5a3573f92c96f9aebf8 Mon Sep 17 00:00:00 2001 From: Ioannis Canellos Date: Wed, 10 Jul 2024 13:55:41 +0300 Subject: [PATCH] feat: introduce build item to control kubernetes generated output directory --- .../CustomKubernetesOutputDirBuildItem.java | 23 ++++++ .../deployment/KubernetesProcessor.java | 11 ++- .../KubernetesWithCustomOutputDirTest.java | 72 +++++++++++++++++++ 3 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/CustomKubernetesOutputDirBuildItem.java create mode 100644 integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithCustomOutputDirTest.java diff --git a/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/CustomKubernetesOutputDirBuildItem.java b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/CustomKubernetesOutputDirBuildItem.java new file mode 100644 index 0000000000000..fcdff93b7800a --- /dev/null +++ b/extensions/kubernetes/spi/src/main/java/io/quarkus/kubernetes/spi/CustomKubernetesOutputDirBuildItem.java @@ -0,0 +1,23 @@ +package io.quarkus.kubernetes.spi; + +import java.nio.file.Path; + +import io.quarkus.builder.item.SimpleBuildItem; + +/** + * Build item that allows us to supply a custom output dir instead of defaulting to {project.target.dir}/kubernetes + * It's different from the {@link KubernetesOutputDirBuildItem} as it's used to communicate the intention to override the dir + * while {@link KubernetesOutputDirBuildItem} is used to communicate the effective output dir. + */ +public final class CustomKubernetesOutputDirBuildItem extends SimpleBuildItem { + + private final Path outputDir; + + public CustomKubernetesOutputDirBuildItem(Path outputDir) { + this.outputDir = outputDir; + } + + public Path getOutputDir() { + return outputDir; + } +} diff --git a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java index dbf5ab08bb887..e241341fc5fa4 100644 --- a/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java +++ b/extensions/kubernetes/vanilla/deployment/src/main/java/io/quarkus/kubernetes/deployment/KubernetesProcessor.java @@ -51,6 +51,7 @@ import io.quarkus.deployment.util.FileUtil; import io.quarkus.kubernetes.spi.ConfigurationSupplierBuildItem; import io.quarkus.kubernetes.spi.ConfiguratorBuildItem; +import io.quarkus.kubernetes.spi.CustomKubernetesOutputDirBuildItem; import io.quarkus.kubernetes.spi.CustomProjectRootBuildItem; import io.quarkus.kubernetes.spi.DecoratorBuildItem; import io.quarkus.kubernetes.spi.DekorateOutputBuildItem; @@ -115,6 +116,7 @@ public void build(ApplicationInfoBuildItem applicationInfo, List decorators, BuildProducer dekorateSessionProducer, Optional customProjectRoot, + Optional customOutputDir, BuildProducer generatedResourceProducer, BuildProducer generatedKubernetesResourceProducer, BuildProducer outputDirectoryBuildItemBuildProducer) { @@ -187,8 +189,13 @@ public void build(ApplicationInfoBuildItem applicationInfo, } }); - Path targetDirectory = getEffectiveOutputDirectory(kubernetesConfig, project.getRoot(), - outputTarget.getOutputDirectory()); + //The targetDirectory should be the custom if provided, oterwise the 'default' output directory. + //I this case 'default' means that one that we used until now (up until we introduced the ability to override). + Path targetDirectory = customOutputDir + .map(c -> c.getOutputDir()) + .map(d -> d.isAbsolute() ? d : project.getRoot().resolve(d)) + .orElseGet(() -> getEffectiveOutputDirectory(kubernetesConfig, project.getRoot(), + outputTarget.getOutputDirectory())); outputDirectoryBuildItemBuildProducer.produce(new KubernetesOutputDirectoryBuildItem(targetDirectory)); diff --git a/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithCustomOutputDirTest.java b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithCustomOutputDirTest.java new file mode 100644 index 0000000000000..4748fe0e1864d --- /dev/null +++ b/integration-tests/kubernetes/quarkus-standard-way/src/test/java/io/quarkus/it/kubernetes/KubernetesWithCustomOutputDirTest.java @@ -0,0 +1,72 @@ +package io.quarkus.it.kubernetes; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.quarkus.builder.BuildContext; +import io.quarkus.kubernetes.spi.CustomKubernetesOutputDirBuildItem; +import io.quarkus.kubernetes.spi.CustomProjectRootBuildItem; +import io.quarkus.test.ProdBuildResults; +import io.quarkus.test.ProdModeTestBuildStep; +import io.quarkus.test.ProdModeTestResults; +import io.quarkus.test.QuarkusProdModeTest; + +public class KubernetesWithCustomOutputDirTest { + + private static final String APP_NAME = "kubernetes-with-custom-output-dir"; + @RegisterExtension + static final QuarkusProdModeTest config = new QuarkusProdModeTest() + .withApplicationRoot((jar) -> jar.addClasses(GreetingResource.class)) + .setApplicationName(APP_NAME) + .setApplicationVersion("0.1-SNAPSHOT") + .addBuildChainCustomizerEntries( + new QuarkusProdModeTest.BuildChainCustomizerEntry(CustomProjectRootBuildItemProducerProdMode.class, + List.of(CustomProjectRootBuildItem.class, CustomKubernetesOutputDirBuildItem.class), + Collections.emptyList())); + + @ProdBuildResults + private ProdModeTestResults prodModeTestResults; + + @Test + public void assertGeneratedResources() throws IOException { + final Path kubernetesDir = prodModeTestResults.getBuildDir().getParent().resolve("custom-sources") + .resolve(".kubernetes"); + assertThat(kubernetesDir) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.json")) + .isDirectoryContaining(p -> p.getFileName().endsWith("kubernetes.yml")) + .satisfies(p -> assertThat(p.toFile().listFiles()).hasSize(2)); + List kubernetesList = DeserializationUtil.deserializeAsList(kubernetesDir.resolve("kubernetes.yml")); + assertThat(kubernetesList).filteredOn(h -> h.getMetadata().getName().equals(APP_NAME)) + .filteredOn(e -> e instanceof Deployment).singleElement().satisfies(d -> { + assertThat(d.getMetadata()).satisfies(m -> { + assertThat(m.getName()).isEqualTo(APP_NAME); + }); + }); + } + + public static class CustomProjectRootBuildItemProducerProdMode extends ProdModeTestBuildStep { + + public CustomProjectRootBuildItemProducerProdMode(Map testContext) { + super(testContext); + } + + @Override + public void execute(BuildContext context) { + context.produce(new CustomProjectRootBuildItem( + (Path) getTestContext().get(QuarkusProdModeTest.BUILD_CONTEXT_CUSTOM_SOURCES_PATH_KEY))); + context.produce(new CustomKubernetesOutputDirBuildItem( + ((Path) getTestContext().get(QuarkusProdModeTest.BUILD_CONTEXT_CUSTOM_SOURCES_PATH_KEY)) + .resolve(".kubernetes"))); + } + } +}