diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java index 1ecac712bba59..d47a5b35c1f5d 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportCamelMain.java @@ -28,10 +28,12 @@ import org.apache.camel.catalog.DefaultCamelCatalog; import org.apache.camel.dsl.jbang.core.common.CommandLineHelper; import org.apache.camel.dsl.jbang.core.common.RuntimeUtil; +import org.apache.camel.dsl.jbang.core.common.VersionHelper; import org.apache.camel.tooling.maven.MavenGav; import org.apache.camel.util.CamelCaseOrderedProperties; import org.apache.camel.util.FileUtil; import org.apache.camel.util.IOHelper; +import org.apache.camel.util.ObjectHelper; import org.apache.commons.io.FileUtils; class ExportCamelMain extends Export { @@ -165,9 +167,12 @@ private void createMavenPom(File settings, File profile, File pom, Set d String context = IOHelper.loadText(is); IOHelper.close(is); - if (camelVersion == null) { - CamelCatalog catalog = new DefaultCamelCatalog(); - camelVersion = catalog.getCatalogVersion(); + CamelCatalog catalog = new DefaultCamelCatalog(); + if (ObjectHelper.isEmpty(camelVersion)) { + camelVersion = catalog.getLoadedVersion(); + } + if (ObjectHelper.isEmpty(camelVersion)) { + camelVersion = VersionHelper.extractCamelVersion(); } context = context.replaceAll("\\{\\{ \\.GroupId }}", ids[0]); diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java index 68a2af582baf4..07cbe64ff9d6a 100644 --- a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java +++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportSpringBoot.java @@ -36,6 +36,7 @@ import org.apache.camel.util.CamelCaseOrderedProperties; import org.apache.camel.util.FileUtil; import org.apache.camel.util.IOHelper; +import org.apache.camel.util.ObjectHelper; import org.apache.commons.io.FileUtils; class ExportSpringBoot extends Export { @@ -158,6 +159,12 @@ private void createMavenPom(File settings, File profile, File pom, Set d String repos = getMavenRepositories(settings, prop, camelSpringBootVersion); CamelCatalog catalog = CatalogLoader.loadSpringBootCatalog(repos, camelSpringBootVersion); + if (ObjectHelper.isEmpty(camelVersion)) { + camelVersion = catalog.getLoadedVersion(); + } + if (ObjectHelper.isEmpty(camelVersion)) { + camelVersion = VersionHelper.extractCamelVersion(); + } // First try to load a specialized template from the catalog, if the catalog does not provide it // fallback to the template defined in camel-jbang-core @@ -169,8 +176,6 @@ private void createMavenPom(File settings, File profile, File pom, Set d context = readResourceTemplate("templates/" + pomTemplateName); } - String camelVersion = catalog.getLoadedVersion(); - context = context.replaceAll("\\{\\{ \\.GroupId }}", ids[0]); context = context.replaceAll("\\{\\{ \\.ArtifactId }}", ids[1]); context = context.replaceAll("\\{\\{ \\.Version }}", ids[2]); diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java index e51633dd5b50a..2a3d8ebbe078c 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesBaseCommand.java @@ -83,8 +83,6 @@ protected NonNamespaceOperation pods() { /** * Gets Kubernetes client. In case custom kubeConfig option is set initializes the client with the config otherwise * uses default client. - * - * @return */ protected KubernetesClient client() { if (kubernetesClient == null) { @@ -100,8 +98,6 @@ protected KubernetesClient client() { /** * Sets the Kubernetes client. - * - * @param kubernetesClient */ public KubernetesBaseCommand withClient(KubernetesClient kubernetesClient) { this.kubernetesClient = kubernetesClient; diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java index b1dd1092c7121..f61c65d7bc395 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java @@ -138,15 +138,13 @@ public class KubernetesRun extends KubernetesBaseCommand { description = "The target cluster type. Special configurations may be applied to different cluster types such as Kind or Minikube.") String clusterType; - @CommandLine.Option(names = { "--image-build" }, - defaultValue = "true", - description = "Weather to build container image as part of the run.") + @CommandLine.Option(names = { "--image-build" }, defaultValue = "true", + description = "Whether to build container image as part of the run.") boolean imageBuild = true; - @CommandLine.Option(names = { "--image-push" }, - defaultValue = "true", - description = "Weather to push image to given image registry as part of the run.") - boolean imagePush = true; + @CommandLine.Option(names = { "--image-push" }, defaultValue = "false", + description = "Whether to push image to given image registry as part of the run.") + boolean imagePush = false; @CommandLine.Option(names = { "--image-platforms" }, description = "List of target platforms. Each platform is defined using the pattern.") @@ -242,6 +240,11 @@ public KubernetesRun(CamelJBangMain main) { super(main); } + public KubernetesRun(CamelJBangMain main, String[] files) { + super(main); + filePaths = files; + } + public Integer doCall() throws Exception { String projectName = getProjectName(); @@ -266,11 +269,8 @@ public Integer doCall() throws Exception { } if (output != null) { - if (RuntimeType.quarkus == runtime) { - exit = buildQuarkus(workingDir); - } else if (RuntimeType.springBoot == runtime) { - exit = buildSpringBoot(workingDir); - } + + exit = buildProject(workingDir); if (exit != 0) { printer().println("Project build failed!"); @@ -295,11 +295,7 @@ public Integer doCall() throws Exception { return 0; } - if (RuntimeType.quarkus == runtime) { - exit = deployQuarkus(workingDir); - } else if (RuntimeType.springBoot == runtime) { - exit = deploySpringBoot(workingDir); - } + exit = deployProject(workingDir); if (exit != 0) { printer().println("Deployment to %s failed!".formatted(Optional.ofNullable(clusterType) @@ -309,7 +305,7 @@ public Integer doCall() throws Exception { if (dev) { DefaultCamelContext reloadContext = new DefaultCamelContext(false); - configureFileWatch(reloadContext, workingDir); + configureFileWatch(reloadContext, export, workingDir); reloadContext.start(); if (cleanup) { @@ -318,6 +314,14 @@ public Integer doCall() throws Exception { } if (dev || wait || logs) { + + String kubectlCmd = "kubectl get pod"; + if (namespace != null) { + kubectlCmd += " -n %s".formatted(namespace); + } + kubectlCmd += " -l %s=%s".formatted(BaseTrait.INTEGRATION_LABEL, projectName); + printer().println(kubectlCmd); + client(Pod.class).withLabel(BaseTrait.INTEGRATION_LABEL, projectName) .waitUntilCondition(it -> "Running".equals(it.getStatus().getPhase()), 10, TimeUnit.MINUTES); } @@ -407,10 +411,10 @@ private void installShutdownInterceptor(String projectName, String workingDir) { Runtime.getRuntime().addShutdownHook(task); } - private Integer buildQuarkus(String workingDir) throws IOException, InterruptedException { - printer().println("Building Quarkus application ..."); + private Integer buildProject(String workingDir) throws IOException, InterruptedException { + printer().println("Building Camel application ..."); - // Run Quarkus build via Maven + // Run build via Maven String mvnw = "/mvnw"; if (FileUtil.isWindows()) { mvnw = "/mvnw.cmd"; @@ -437,11 +441,11 @@ private Integer buildQuarkus(String workingDir) throws IOException, InterruptedE return 0; } - private Integer deployQuarkus(String workingDir) throws IOException, InterruptedException { + private Integer deployProject(String workingDir) throws IOException, InterruptedException { printer().println("Deploying to %s ...".formatted(Optional.ofNullable(clusterType) .map(StringHelper::capitalize).orElse("Kubernetes"))); - // Run Quarkus build via Maven + // Run build via Maven String mvnw = "/mvnw"; if (FileUtil.isWindows()) { mvnw = "/mvnw.cmd"; @@ -454,29 +458,46 @@ private Integer deployQuarkus(String workingDir) throws IOException, Interrupted args.add("--file"); args.add(workingDir); - if (imagePlatforms != null) { - args.add("-Dquarkus.jib.platforms=%s".formatted(imagePlatforms)); - } + if (runtime == RuntimeType.quarkus) { - if (imageBuild) { - args.add("-Dquarkus.container-image.build=true"); - } + if (imagePlatforms != null) { + args.add("-Dquarkus.jib.platforms=%s".formatted(imagePlatforms)); + } - if (imagePush) { - args.add("-Dquarkus.container-image.push=true"); - } + if (imageBuild) { + args.add("-Dquarkus.container-image.build=true"); + } + + if (imagePush) { + args.add("-Dquarkus.container-image.push=true"); + } + + if (ClusterType.OPENSHIFT.isEqualTo(clusterType)) { + args.add("-Dquarkus.openshift.deploy=true"); + } else { + args.add("-Dquarkus.kubernetes.deploy=true"); + } + + args.add("package"); - if (ClusterType.OPENSHIFT.isEqualTo(clusterType)) { - args.add("-Dquarkus.openshift.deploy=true"); } else { - args.add("-Dquarkus.kubernetes.deploy=true"); - } - if (namespace != null) { - args.add("-Dquarkus.kubernetes.namespace=%s".formatted(namespace)); + if (!imageBuild) { + args.add("-Djkube.skip.build=true"); + } + + if (imagePush) { + args.add("-Djkube.%s.push=true".formatted(imageBuilder)); + } + + if (namespace != null) { + args.add("-Djkube.namespace=%s".formatted(namespace)); + } + + args.add("package"); + args.add("k8s:deploy"); } - args.add("package"); pb.command(args.toArray(String[]::new)); pb.inheritIO(); // run in foreground (with IO so logs are visible) @@ -491,22 +512,7 @@ private Integer deployQuarkus(String workingDir) throws IOException, Interrupted return 0; } - private Integer buildSpringBoot(String workingDir) { - printer().println("Building Spring Boot application ..."); - - // TODO: implement SpringBoot project build - return 0; - } - - private Integer deploySpringBoot(String workingDir) { - printer().println("Deploying to %s ...".formatted(Optional.ofNullable(clusterType) - .map(StringHelper::capitalize).orElse("Kubernetes"))); - - // TODO: implement SpringBoot Kubernetes deployment - return 0; - } - - private void configureFileWatch(DefaultCamelContext camelContext, String workingDir) + private void configureFileWatch(DefaultCamelContext camelContext, KubernetesExport export, String workingDir) throws Exception { String watchDir = "."; FileFilter filter = null; @@ -525,17 +531,9 @@ private void configureFileWatch(DefaultCamelContext camelContext, String working = new FileWatcherResourceReloadStrategy(watchDir); reloadStrategy.setResourceReload((name, resource) -> { printer().printf("Reloading project due to file change: %s%n", FileUtil.stripPath(name)); - KubernetesExport export = configureExport(workingDir); int refresh = export.export(); if (refresh == 0) { - if (RuntimeType.quarkus == runtime) { - deployQuarkus(workingDir); - } else if (RuntimeType.springBoot == runtime) { - deploySpringBoot(workingDir); - } - } else { - // print export command output with error details - printer().printf("Reloading project failed - export failed with code: %d%n", refresh); + deployProject(workingDir); } }); if (filter != null) { @@ -563,5 +561,4 @@ private String getProjectName() { throw new RuntimeCamelException( "Failed to resolve project name - please provide --gav, --image option or at least one source file"); } - } diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java index b2d7f934765c2..afcee4a94a5f5 100644 --- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java +++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRunTest.java @@ -17,17 +17,27 @@ package org.apache.camel.dsl.jbang.core.commands.kubernetes; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; +import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.apps.Deployment; import org.apache.camel.RuntimeCamelException; import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait; +import org.apache.camel.dsl.jbang.core.common.RuntimeType; import org.apache.camel.dsl.jbang.core.common.StringPrinter; +import org.apache.camel.dsl.jbang.core.common.VersionHelper; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import picocli.CommandLine; class KubernetesRunTest extends KubernetesBaseTest { @@ -35,13 +45,24 @@ class KubernetesRunTest extends KubernetesBaseTest { @BeforeEach public void setup() { + // Set Camel version with system property value, usually set via Maven surefire plugin + // In case you run this test via local Java IDE you need to provide the system property or a default value here + VersionHelper.setCamelVersion(System.getProperty("camel.version", "")); printer = new StringPrinter(); } - @Test - public void shouldHandleMissingSourceFile() throws Exception { - KubernetesRun command = createCommand(); - command.filePaths = new String[] { "mickey-mouse.groovy" }; + private static Stream runtimeProvider() { + return Stream.of( + Arguments.of(RuntimeType.main), + Arguments.of(RuntimeType.springBoot), + Arguments.of(RuntimeType.quarkus)); + } + + @ParameterizedTest + @MethodSource("runtimeProvider") + public void shouldHandleMissingSourceFile(RuntimeType rt) throws Exception { + KubernetesRun command = createCommand(new String[] { "mickey-mouse.groovy" }, + "--output=yaml", "--runtime=" + rt.runtime()); int exit = command.doCall(); Assertions.assertEquals(1, exit); @@ -49,10 +70,13 @@ public void shouldHandleMissingSourceFile() throws Exception { Assertions.assertTrue(printer.getOutput().contains("Project export failed")); } - @Test - public void shouldGenerateKubernetesManifest() throws Exception { - KubernetesRun command = createCommand(); - command.filePaths = new String[] { "classpath:route.yaml" }; + @ParameterizedTest + @MethodSource("runtimeProvider") + public void shouldGenerateKubernetesManifest(RuntimeType rt) throws Exception { + KubernetesRun command = createCommand(new String[] { "classpath:route.yaml" }, + "--image-registry=quay.io", "--image-group=camel-test", "--output=yaml", + "--trait", "container.image-pull-policy=IfNotPresent", + "--runtime=" + rt.runtime()); int exit = command.doCall(); Assertions.assertEquals(0, exit); @@ -68,30 +92,30 @@ public void shouldGenerateKubernetesManifest() throws Exception { Assertions.assertEquals("route", deployment.getMetadata().getName()); Assertions.assertEquals(1, deployment.getSpec().getTemplate().getSpec().getContainers().size()); + Container container = deployment.getSpec().getTemplate().getSpec().getContainers().get(0); + Assertions.assertEquals("route", container.getName()); Assertions.assertEquals("route", deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL)); - Assertions.assertEquals("route", deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getName()); - Assertions.assertEquals(3, deployment.getSpec().getSelector().getMatchLabels().size()); Assertions.assertEquals("route", deployment.getSpec().getSelector().getMatchLabels().get(BaseTrait.INTEGRATION_LABEL)); - Assertions.assertEquals("docker.io/camel-test/route:1.0-SNAPSHOT", - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage()); - Assertions.assertEquals("Always", - deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImagePullPolicy()); + Assertions.assertEquals("quay.io/camel-test/route:1.0-SNAPSHOT", container.getImage()); + Assertions.assertEquals("IfNotPresent", container.getImagePullPolicy()); } - @Test - public void shouldHandleUnsupportedOutputFormat() throws Exception { - KubernetesRun command = createCommand(); - command.filePaths = new String[] { "classpath:route.yaml" }; - command.output = "wrong"; + @ParameterizedTest + @MethodSource("runtimeProvider") + public void shouldHandleUnsupportedOutputFormat(RuntimeType rt) throws Exception { + KubernetesRun command = createCommand(new String[] { "classpath:route.yaml" }, + "--output=wrong", "--runtime=" + rt.runtime()); Assertions.assertEquals(1, command.doCall()); Assertions.assertTrue(printer.getOutput().endsWith("Unsupported output format 'wrong' (supported: yaml, json)")); } - private KubernetesRun createCommand() { - KubernetesRun command = new KubernetesRun(new CamelJBangMain().withPrinter(printer)); - command.output = "yaml"; - command.imageGroup = "camel-test"; + private KubernetesRun createCommand(String[] files, String... args) { + var argsArr = Optional.ofNullable(args).orElse(new String[0]); + var argsLst = new ArrayList<>(Arrays.asList(argsArr)); + var jbangMain = new CamelJBangMain().withPrinter(printer); + KubernetesRun command = new KubernetesRun(jbangMain, files); + CommandLine.populateCommand(command, argsLst.toArray(new String[0])); command.imageBuild = false; command.imagePush = false; return command;