This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new e5134e1fc7d [CAMEL-21068] Add support for `camel kubernetes run` for
all runtimes (#15514)
e5134e1fc7d is described below
commit e5134e1fc7d0296c671e54593dc417a44f2e3bc2
Author: Thomas Diesler <[email protected]>
AuthorDate: Wed Sep 11 13:45:45 2024 +0200
[CAMEL-21068] Add support for `camel kubernetes run` for all runtimes
(#15514)
---
.../dsl/jbang/core/commands/ExportCamelMain.java | 11 +-
.../dsl/jbang/core/commands/ExportSpringBoot.java | 9 +-
.../commands/kubernetes/KubernetesBaseCommand.java | 4 -
.../core/commands/kubernetes/KubernetesRun.java | 127 ++++++++++-----------
.../commands/kubernetes/KubernetesRunTest.java | 72 ++++++++----
5 files changed, 125 insertions(+), 98 deletions(-)
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 1ecac712bba..d47a5b35c1f 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.CamelCatalog;
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 @@ class ExportCamelMain extends Export {
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 68a2af582ba..07cbe64ff9d 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.tooling.model.ArtifactModel;
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 @@ class ExportSpringBoot extends Export {
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 @@ class ExportSpringBoot extends Export {
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 e51633dd5b5..2a3d8ebbe07 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 @@ public abstract class KubernetesBaseCommand extends
CamelCommand {
/**
* 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 @@ public abstract class KubernetesBaseCommand extends
CamelCommand {
/**
* 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 b1dd1092c71..f61c65d7bc3 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 class KubernetesRun extends KubernetesBaseCommand {
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 class KubernetesRun extends KubernetesBaseCommand {
}
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 class KubernetesRun extends KubernetesBaseCommand {
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 class KubernetesRun extends KubernetesBaseCommand {
if (dev) {
DefaultCamelContext reloadContext = new DefaultCamelContext(false);
- configureFileWatch(reloadContext, workingDir);
+ configureFileWatch(reloadContext, export, workingDir);
reloadContext.start();
if (cleanup) {
@@ -318,6 +314,14 @@ public class KubernetesRun extends KubernetesBaseCommand {
}
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 @@ public class KubernetesRun extends KubernetesBaseCommand {
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 @@ public class KubernetesRun extends KubernetesBaseCommand {
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 @@ public class KubernetesRun extends KubernetesBaseCommand {
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 @@ public class KubernetesRun extends KubernetesBaseCommand {
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 @@ public class KubernetesRun extends KubernetesBaseCommand {
= 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 @@ public class KubernetesRun extends KubernetesBaseCommand {
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 b2d7f934765..afcee4a94a5 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<Arguments> 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 @@ class KubernetesRunTest extends KubernetesBaseTest {
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 @@ class KubernetesRunTest extends KubernetesBaseTest {
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;