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 caf79c59b2ec CAMEL-23491: Add known 3rd party plugin catalog to
camel-jbang
caf79c59b2ec is described below
commit caf79c59b2ec3511792fbd0fd21eb17d431f42c5
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue May 26 13:19:47 2026 +0200
CAMEL-23491: Add known 3rd party plugin catalog to camel-jbang
Add a curated catalog of known 3rd party plugins (known-plugins.json)
so users can install them by name without specifying GAV coordinates
(e.g., camel plugin add forage). Add vendor field to PluginType to
distinguish ASF vs Community plugins. The camel plugin get --all
command now shows active, supported (ASF), and known 3rd party
(Community) sections. Users can pin a specific version with --version
flag; defaults to LATEST for 3rd party plugins.
Closes #23443
---
.../modules/ROOT/pages/camel-jbang.adoc | 29 +++++++++++++--
.../dsl/jbang/core/commands/plugin/PluginAdd.java | 33 +++++++++++++++--
.../dsl/jbang/core/commands/plugin/PluginGet.java | 40 +++++++++++++++++++--
.../camel/dsl/jbang/core/common/PluginHelper.java | 33 +++++++++++++++++
.../camel/dsl/jbang/core/common/PluginType.java | 22 +++++++-----
.../src/main/resources/known-plugins.json | 20 +++++++++++
.../jbang/core/commands/plugin/PluginGetTest.java | 41 ++++++++++++----------
7 files changed, 184 insertions(+), 34 deletions(-)
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index 6499e6ddc9ef..b4768b3db61b 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -1251,11 +1251,20 @@ camel plugin get --all
----
Supported plugins:
- NAME COMMAND DEPENDENCY
DESCRIPTION
- kubernetes kubernetes org.apache.camel:camel-jbang-plugin-kubernetes Run
Camel applications on Kubernetes
- generate generate org.apache.camel:camel-jbang-plugin-generate
Generate code such as DTOs
+ NAME COMMAND VENDOR DEPENDENCY
DESCRIPTION
+ kubernetes kubernetes ASF
org.apache.camel:camel-jbang-plugin-kubernetes Run Camel applications on
Kubernetes
+ generate generate ASF org.apache.camel:camel-jbang-plugin-generate
Generate code such as DTOs
+ ...
+
+Known 3rd party plugins:
+
+ NAME COMMAND VENDOR DEPENDENCY
DESCRIPTION
+ forage forage Community io.kaoto.forage:camel-jbang-plugin-forage
Production ready Apache Camel components configurations via properties
+ camel-kit kit Community io.github.luigidemasi:camel-kit-jbang-plugin
Design Apache Camel Integrations with AI
----
+The `VENDOR` column indicates whether the plugin is from `ASF` (Apache
Software Foundation) or `Community` (3rd party).
+
In case you want to enable a plugin and its functionality, you can add it as
follows:
[source,bash]
@@ -1272,6 +1281,20 @@ camel plugin add generate
This adds the plugin, and all subcommands are now available for execution.
+Known 3rd party plugins can be installed by name as well. For example:
+
+[source,bash]
+----
+camel plugin add forage
+----
+
+To install a specific version of a 3rd party plugin, use the `--version`
option:
+
+[source,bash]
+----
+camel plugin add forage --version=1.2.3
+----
+
You can list the currently installed plugins with:
[source,bash]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAdd.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAdd.java
index a1ad1998f9ad..82c1c0b15de8 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAdd.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginAdd.java
@@ -21,6 +21,7 @@ import java.util.Optional;
import org.apache.camel.catalog.CamelCatalog;
import org.apache.camel.catalog.DefaultCamelCatalog;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
import org.apache.camel.dsl.jbang.core.common.PluginType;
import org.apache.camel.util.json.JsonObject;
import picocli.CommandLine;
@@ -89,6 +90,30 @@ public class PluginAdd extends PluginBaseCommand {
if (firstVersion == null) {
firstVersion = camelPlugin.get().getFirstVersion();
}
+ } else {
+ Optional<JsonObject> known = PluginHelper.findKnownPlugin(name);
+ if (known.isPresent()) {
+ JsonObject kp = known.get();
+ if (command == null) {
+ command = kp.getString("command");
+ }
+ if (description == null) {
+ description = kp.getString("description");
+ }
+ if (firstVersion == null) {
+ firstVersion = kp.getString("firstVersion");
+ }
+ if (gav == null) {
+ groupId = kp.getStringOrDefault("groupId", groupId);
+ artifactId = kp.getString("artifactId");
+ }
+ if (repositories == null) {
+ String knownRepos = kp.getString("repos");
+ if (knownRepos != null) {
+ repositories = knownRepos;
+ }
+ }
+ }
}
if (command == null) {
@@ -111,8 +136,12 @@ public class PluginAdd extends PluginBaseCommand {
if (gav == null && (groupId != null && artifactId != null)) {
if (version == null) {
- CamelCatalog catalog = new DefaultCamelCatalog();
- version = catalog.getCatalogVersion();
+ if ("org.apache.camel".equals(groupId)) {
+ CamelCatalog catalog = new DefaultCamelCatalog();
+ version = catalog.getCatalogVersion();
+ } else {
+ version = "LATEST";
+ }
}
gav = "%s:%s:%s".formatted(groupId, artifactId, version);
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGet.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGet.java
index 9e17ae314ec7..0395a5f5f720 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGet.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGet.java
@@ -25,6 +25,7 @@ import com.github.freva.asciitable.Column;
import com.github.freva.asciitable.HorizontalAlign;
import com.github.freva.asciitable.OverflowBehaviour;
import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.PluginHelper;
import org.apache.camel.dsl.jbang.core.common.PluginType;
import org.apache.camel.util.json.JsonObject;
import picocli.CommandLine;
@@ -59,7 +60,8 @@ public class PluginGet extends PluginBaseCommand {
= details.getStringOrDefault("description", "Plugin %s
called with command %s".formatted(name, command));
String repos = details.getString("repos");
- rows.add(new Row(name, command, dependency, description, repos));
+ String vendor = resolveVendor(name);
+ rows.add(new Row(name, command, dependency, description, repos,
vendor));
});
printRows(rows);
@@ -71,7 +73,7 @@ public class PluginGet extends PluginBaseCommand {
String dependency =
"org.apache.camel:camel-jbang-plugin-%s".formatted(camelPlugin.getCommand());
rows.add(new Row(
camelPlugin.getName(), camelPlugin.getCommand(),
dependency,
- camelPlugin.getDescription(),
camelPlugin.getRepos()));
+ camelPlugin.getDescription(),
camelPlugin.getRepos(), camelPlugin.getVendor()));
}
}
@@ -82,11 +84,41 @@ public class PluginGet extends PluginBaseCommand {
printRows(rows);
}
+
+ rows.clear();
+ List<JsonObject> knownPlugins = PluginHelper.loadKnownPlugins();
+ for (JsonObject kp : knownPlugins) {
+ String kpName = kp.getString("name");
+ if (plugins.get(kpName) == null &&
PluginType.findByName(kpName).isEmpty()) {
+ String dep = kp.getString("groupId") != null &&
kp.getString("artifactId") != null
+ ? "%s:%s".formatted(kp.getString("groupId"),
kp.getString("artifactId"))
+ : kp.getStringOrDefault("dependency", "");
+ rows.add(new Row(
+ kpName, kp.getString("command"), dep,
+ kp.getString("description"), kp.getString("repos"),
+ kp.getStringOrDefault("vendor", "Community")));
+ }
+ }
+
+ if (!rows.isEmpty()) {
+ printer().println();
+ printer().println("Known 3rd party plugins:");
+ printer().println();
+
+ printRows(rows);
+ }
}
return 0;
}
+ private String resolveVendor(String name) {
+ return PluginType.findByName(name)
+ .map(PluginType::getVendor)
+ .or(() -> PluginHelper.findKnownPlugin(name).map(kp ->
kp.getStringOrDefault("vendor", "Community")))
+ .orElse("");
+ }
+
private void printRows(List<Row> rows) {
if (!rows.isEmpty()) {
printer().println(AsciiTable.getTable(AsciiTable.NO_BORDERS, rows,
Arrays.asList(
@@ -94,6 +126,8 @@ public class PluginGet extends PluginBaseCommand {
.with(r -> r.name),
new
Column().header("COMMAND").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
.with(r -> r.command),
+ new
Column().header("VENDOR").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
+ .with(r -> r.vendor != null ? r.vendor : ""),
new
Column().header("DEPENDENCY").headerAlign(HorizontalAlign.LEFT).dataAlign(HorizontalAlign.LEFT)
.with(r -> r.dependency),
new
Column().visible(repos).header("REPOSITORY").headerAlign(HorizontalAlign.LEFT)
@@ -105,6 +139,6 @@ public class PluginGet extends PluginBaseCommand {
}
}
- private record Row(String name, String command, String dependency, String
description, String repos) {
+ private record Row(String name, String command, String dependency, String
description, String repos, String vendor) {
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
index 36b01f5e3eda..0fa2eb0a1f55 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginHelper.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;
+import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
@@ -50,6 +51,7 @@ import org.apache.camel.spi.FactoryFinder;
import org.apache.camel.support.ObjectHelper;
import org.apache.camel.tooling.maven.MavenGav;
import org.apache.camel.util.IOHelper;
+import org.apache.camel.util.json.JsonArray;
import org.apache.camel.util.json.JsonObject;
import org.apache.camel.util.json.Jsoner;
import picocli.CommandLine;
@@ -575,6 +577,37 @@ public final class PluginHelper {
}
}
+ /**
+ * Loads the list of known 3rd party plugins from the classpath resource
known-plugins.json.
+ */
+ public static List<JsonObject> loadKnownPlugins() {
+ try (InputStream is =
PluginHelper.class.getClassLoader().getResourceAsStream("known-plugins.json")) {
+ if (is != null) {
+ String text = new String(is.readAllBytes(),
StandardCharsets.UTF_8);
+ JsonArray arr = (JsonArray) Jsoner.deserialize(text);
+ List<JsonObject> result = new ArrayList<>(arr.size());
+ for (Object o : arr) {
+ if (o instanceof JsonObject jo) {
+ result.add(jo);
+ }
+ }
+ return result;
+ }
+ } catch (Exception e) {
+ // ignore
+ }
+ return List.of();
+ }
+
+ /**
+ * Finds a known 3rd party plugin by name (case-insensitive).
+ */
+ public static Optional<JsonObject> findKnownPlugin(String name) {
+ return loadKnownPlugins().stream()
+ .filter(p -> name.equalsIgnoreCase(p.getString("name")))
+ .findFirst();
+ }
+
public static void enable(PluginType pluginType) {
JsonObject pluginConfig = PluginHelper.getOrCreatePluginConfig();
JsonObject plugins = pluginConfig.getMap("plugins");
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
index 0e3eb1d5df4c..32ab80632d44 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/PluginType.java
@@ -24,26 +24,28 @@ import java.util.Optional;
*/
public enum PluginType {
- KUBERNETES("kubernetes", "kubernetes", "Run Camel applications on
Kubernetes", "4.8.0", null),
- GENERATE("generate", "generate", "Generate code such as DTOs", "4.8.0",
null),
- EDIT("edit", "edit", "Edit Camel files with suggestions", "4.12.0", null),
- TEST("test", "test", "Manage tests for Camel applications", "4.14.0",
null),
- ROUTE_PARSER("route-parser", "route-parser", "Parses Java route and dumps
route structure", "4.17.0", null),
- VALIDATE("validate", "validate", "Validate Camel routes", "4.18.0", null),
- TUI("tui", "tui", "Camel Dashboard", "4.20.0", null);
+ KUBERNETES("kubernetes", "kubernetes", "Run Camel applications on
Kubernetes", "4.8.0", null, "ASF"),
+ GENERATE("generate", "generate", "Generate code such as DTOs", "4.8.0",
null, "ASF"),
+ EDIT("edit", "edit", "Edit Camel files with suggestions", "4.12.0", null,
"ASF"),
+ TEST("test", "test", "Manage tests for Camel applications", "4.14.0",
null, "ASF"),
+ ROUTE_PARSER("route-parser", "route-parser", "Parses Java route and dumps
route structure", "4.17.0", null, "ASF"),
+ VALIDATE("validate", "validate", "Validate Camel routes", "4.18.0", null,
"ASF"),
+ TUI("tui", "tui", "Camel Dashboard", "4.20.0", null, "ASF");
private final String name;
private final String command;
private final String description;
private final String firstVersion;
private final String repos;
+ private final String vendor;
- PluginType(String name, String command, String description, String
firstVersion, String repos) {
+ PluginType(String name, String command, String description, String
firstVersion, String repos, String vendor) {
this.name = name;
this.command = command;
this.description = description;
this.firstVersion = firstVersion;
this.repos = repos;
+ this.vendor = vendor;
}
public static Optional<PluginType> findByName(String name) {
@@ -71,4 +73,8 @@ public enum PluginType {
public String getRepos() {
return repos;
}
+
+ public String getVendor() {
+ return vendor;
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/resources/known-plugins.json
b/dsl/camel-jbang/camel-jbang-core/src/main/resources/known-plugins.json
new file mode 100644
index 000000000000..451b60ef76ba
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/resources/known-plugins.json
@@ -0,0 +1,20 @@
+[
+ {
+ "name": "forage",
+ "command": "forage",
+ "description": "Production ready Apache Camel components configurations
via properties",
+ "firstVersion": "4.10.0",
+ "groupId": "io.kaoto.forage",
+ "artifactId": "camel-jbang-plugin-forage",
+ "vendor": "Community"
+ },
+ {
+ "name": "camel-kit",
+ "command": "kit",
+ "description": "Design Apache Camel Integrations with AI",
+ "firstVersion": "4.10.0",
+ "groupId": "io.github.luigidemasi",
+ "artifactId": "camel-kit-jbang-plugin",
+ "vendor": "Community"
+ }
+]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
index 9fef8e54f0bb..ba950afb31bb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/plugin/PluginGetTest.java
@@ -55,10 +55,10 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
List<String> output = printer.getLines();
Assertions.assertEquals(2, output.size());
- Assertions.assertEquals("NAME COMMAND DEPENDENCY
DESCRIPTION",
+ Assertions.assertEquals("NAME COMMAND VENDOR DEPENDENCY
DESCRIPTION",
output.get(0));
Assertions.assertEquals(
- "kubernetes kubernetes
org.apache.camel:camel-jbang-plugin-kubernetes %s"
+ "kubernetes kubernetes ASF
org.apache.camel:camel-jbang-plugin-kubernetes %s"
.formatted(PluginType.KUBERNETES.getDescription()),
output.get(1));
}
@@ -70,12 +70,13 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
command.doCall();
List<String> output = printer.getLines();
- Assertions.assertEquals(10, output.size());
+ Assertions.assertTrue(output.size() >= 10);
Assertions.assertEquals("Supported plugins:", output.get(0));
- Assertions.assertEquals("NAME COMMAND DEPENDENCY
DESCRIPTION",
+ Assertions.assertEquals(
+ "NAME COMMAND VENDOR DEPENDENCY
DESCRIPTION",
output.get(2));
Assertions.assertEquals(
- "kubernetes kubernetes
org.apache.camel:camel-jbang-plugin-kubernetes %s"
+ "kubernetes kubernetes ASF
org.apache.camel:camel-jbang-plugin-kubernetes %s"
.formatted(PluginType.KUBERNETES.getDescription()),
output.get(3));
}
@@ -97,8 +98,9 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
List<String> output = printer.getLines();
Assertions.assertEquals(2, output.size());
- Assertions.assertEquals("NAME COMMAND DEPENDENCY
DESCRIPTION", output.get(0));
- Assertions.assertEquals("foo foo
org.apache.camel:camel-jbang-plugin-foo Plugin foo called with command foo",
+ Assertions.assertEquals("NAME COMMAND VENDOR DEPENDENCY
DESCRIPTION", output.get(0));
+ Assertions.assertEquals(
+ "foo foo
org.apache.camel:camel-jbang-plugin-foo Plugin foo called with command foo",
output.get(1));
}
@@ -120,43 +122,46 @@ class PluginGetTest extends CamelCommandBaseTestSupport {
command.doCall();
List<String> output = printer.getLines();
- Assertions.assertEquals(13, output.size());
- Assertions.assertEquals("NAME COMMAND DEPENDENCY
DESCRIPTION", output.get(0));
+ Assertions.assertTrue(output.size() >= 13);
+ Assertions.assertEquals("NAME COMMAND VENDOR DEPENDENCY
DESCRIPTION", output.get(0));
Assertions.assertEquals(
- "foo-plugin foo org.apache.camel:foo-plugin:1.0.0
Plugin foo-plugin called with command foo",
+ "foo-plugin foo
org.apache.camel:foo-plugin:1.0.0 Plugin foo-plugin called with command foo",
output.get(1));
Assertions.assertEquals("Supported plugins:", output.get(3));
- Assertions.assertEquals("NAME COMMAND DEPENDENCY
DESCRIPTION",
+ Assertions.assertEquals(
+ "NAME COMMAND VENDOR DEPENDENCY
DESCRIPTION",
output.get(5));
Assertions.assertEquals(
- "kubernetes kubernetes
org.apache.camel:camel-jbang-plugin-kubernetes %s"
+ "kubernetes kubernetes ASF
org.apache.camel:camel-jbang-plugin-kubernetes %s"
.formatted(PluginType.KUBERNETES.getDescription()),
output.get(6));
Assertions.assertEquals(
- "generate generate
org.apache.camel:camel-jbang-plugin-generate %s"
+ "generate generate ASF
org.apache.camel:camel-jbang-plugin-generate %s"
.formatted(PluginType.GENERATE.getDescription()),
output.get(7));
Assertions.assertEquals(
- "edit edit
org.apache.camel:camel-jbang-plugin-edit %s"
+ "edit edit ASF
org.apache.camel:camel-jbang-plugin-edit %s"
.formatted(PluginType.EDIT.getDescription()),
output.get(8));
Assertions.assertEquals(
- "test test
org.apache.camel:camel-jbang-plugin-test %s"
+ "test test ASF
org.apache.camel:camel-jbang-plugin-test %s"
.formatted(PluginType.TEST.getDescription()),
output.get(9));
Assertions.assertEquals(
- "route-parser route-parser
org.apache.camel:camel-jbang-plugin-route-parser %s"
+ "route-parser route-parser ASF
org.apache.camel:camel-jbang-plugin-route-parser %s"
.formatted(PluginType.ROUTE_PARSER.getDescription()),
output.get(10));
Assertions.assertEquals(
- "validate validate
org.apache.camel:camel-jbang-plugin-validate %s"
+ "validate validate ASF
org.apache.camel:camel-jbang-plugin-validate %s"
.formatted(PluginType.VALIDATE.getDescription()),
output.get(11));
Assertions.assertEquals(
- "tui tui
org.apache.camel:camel-jbang-plugin-tui %s"
+ "tui tui ASF
org.apache.camel:camel-jbang-plugin-tui %s"
.formatted(PluginType.TUI.getDescription()),
output.get(12));
+
+ Assertions.assertEquals("Known 3rd party plugins:", output.get(14));
}
}