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 e43bf7a2a959 CAMEL-24821: camel-jbang - choose the settings of an
infra service with --property (#27472)
e43bf7a2a959 is described below
commit e43bf7a2a959fbf463c556b1e4ae6a32454016d5
Author: Karol Krawczyk <[email protected]>
AuthorDate: Thu Oct 8 15:40:02 2026 +0200
CAMEL-24821: camel-jbang - choose the settings of an infra service with
--property (#27472)
Add a repeatable --property key=value option to camel infra run (and
restart), so any setting a test infra service reads from its
container.properties can be chosen from the CLI, for example
--property ollama.model=qwen2.5:0.5b to pull a smaller model.
The properties are set as system properties before the service is
instantiated, and are cleared again when the service stops so a later
run in the same JVM (TUI, tests) does not inherit them. Only the first
'=' separates name and value; a malformed pair is rejected with exit
code 1. --background passes the properties on to the re-executed CLI.
Documented in camel-jbang-dev-services.adoc with the ollama example.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
---
.../ROOT/pages/camel-jbang-ai-providers.adoc | 4 +-
.../ROOT/pages/camel-jbang-dev-services.adoc | 21 ++++
.../jbang-commands/camel-jbang-infra-restart.adoc | 1 +
.../jbang-commands/camel-jbang-infra-run.adoc | 1 +
.../META-INF/camel-jbang-commands-metadata.json | 2 +-
.../jbang/core/commands/infra/InfraRestart.java | 7 ++
.../dsl/jbang/core/commands/infra/InfraRun.java | 109 +++++++++++++++++++--
.../core/commands/infra/InfraRunPropertyTest.java | 98 ++++++++++++++++++
8 files changed, 232 insertions(+), 11 deletions(-)
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-ai-providers.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-ai-providers.adoc
index c8b78c8ca900..40ef81072dc4 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-ai-providers.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-ai-providers.adoc
@@ -44,7 +44,9 @@ See xref:camel-jbang-tui-ai.adoc[Camel TUI AI Panel].
Install Ollama natively for the best performance — the native binary uses GPU
acceleration
(Metal on macOS, CUDA/ROCm on Linux). Running Ollama through Docker (`camel
infra run ollama`)
-bypasses the GPU and makes inference much slower.
+bypasses the GPU and makes inference much slower. The model that container
pulls is chosen with
+`camel infra run ollama --property ollama.model=...`, as
+xref:camel-jbang-dev-services.adoc#_configuring_a_service[Dev Services]
describes.
[source,bash]
----
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-dev-services.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-dev-services.adoc
index ab20b48ddde2..616e9d307953 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-dev-services.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-dev-services.adoc
@@ -64,6 +64,27 @@ Starting service kafka with implementation redpanda
}
----
+== Configuring a service
+
+A service reads its own settings from system properties, so `--property`
passes them through. The
+option is repeatable and takes `key=value`:
+
+[source,bash]
+----
+$ camel infra run ollama --property ollama.model=qwen2.5:0.5b
+
+Starting service ollama
+----
+
+Ollama pulls `granite4:3b` by default, 2.1 GB; `qwen2.5:0.5b` is around 400
MB, which is enough for
+most development and a lot faster to start. The same option sets the embedding
model
+(`ollama.embedding.model`), GPU support (`ollama.container.enable.gpu`,
`enabled` or `disabled`), the
+container image, and the settings of every other service. The property names
are the ones in the
+`container.properties` of the service, in `camel-test-infra-<service>`.
+
+`camel infra restart` takes the options again, the way it takes `--port`, so a
restart keeps the
+model it was started with.
+
== Stopping a service
[source,bash]
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-restart.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-restart.adoc
index b83054fbacee..77e3f37e2676 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-restart.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-restart.adoc
@@ -24,6 +24,7 @@ camel infra restart [options]
| `--kill` | To force killing the process (SIGKILL) when stopping | | boolean
| `--log` | Log container output to console | | boolean
| `--port` | Override the default port for the service | | Integer
+| `--prop,--property` | Service properties, ex.
--property=ollama.model=qwen2.5:0.5b | | List
| `-h,--help` | Display the help and sub-commands | | boolean
|===
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-run.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-run.adoc
index a0d1592f2764..2cd23339ee96 100644
---
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-run.adoc
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-infra-run.adoc
@@ -24,6 +24,7 @@ camel infra run [options]
| `--log` | Log container output to console | | boolean
| `--no-ui` | Do not start companion UI containers | false | boolean
| `--port` | Override the default port for the service | | Integer
+| `--prop,--property` | Service properties, ex.
--property=ollama.model=qwen2.5:0.5b | | List
| `-h,--help` | Display the help and sub-commands | | boolean
|===
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index d4507d17043b..fce709a9ad77 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -18,7 +18,7 @@
{ "name": "get", "fullName": "get", "description": "Get status of Camel
integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.CamelStatus", "options": [ {
"names": "--watch", "description": "Execute periodically and showing output
fullscreen", "javaType": "boolean", "type": "boolean" }, { "names":
"-h,--help", "description": "Display the help and sub-commands", "javaType":
"boolean", "type": "boolean" } ], "subcommands": [ { "name": "activity",
"fullName": " [...]
{ "name": "harden", "fullName": "harden", "description": "Suggest security
hardening for Camel routes using AI\/LLM", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.Harden", "options": [ { "names":
"--api-key", "description": "API key for authentication. Also reads
OPENAI_API_KEY or LLM_API_KEY env vars", "javaType": "java.lang.String",
"type": "string" }, { "names": "--api-type", "description": "API type: 'ollama'
or 'openai' (OpenAI-compatible)", "defaultValue": "ollama", [...]
{ "name": "hawtio", "fullName": "hawtio", "description": "Launch Hawtio
web console", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.Hawtio", "options": [ {
"names": "--host", "description": "Hostname to bind the Hawtio web console to",
"defaultValue": "127.0.0.1", "javaType": "java.lang.String", "type": "string"
}, { "names": "--openUrl", "description": "To automatic open Hawtio web console
in the web browser", "defaultValue": "true", "javaType": "boolean", "type":
[...]
- { "name": "infra", "fullName": "infra", "description": "List and Run
external services for testing and prototyping", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ {
"names": "--json", "description": "Output in JSON Format", "javaType":
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ],
"subcommands": [ { "name": "get", "fullName": "infra [...]
+ { "name": "infra", "fullName": "infra", "description": "List and Run
external services for testing and prototyping", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.infra.InfraCommand", "options": [ {
"names": "--json", "description": "Output in JSON Format", "javaType":
"boolean", "type": "boolean" }, { "names": "-h,--help", "description": "Display
the help and sub-commands", "javaType": "boolean", "type": "boolean" } ],
"subcommands": [ { "name": "get", "fullName": "infra [...]
{ "name": "init", "fullName": "init", "description": "Creates a new Camel
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Init",
"options": [ { "names": "--clean-dir,--clean-directory", "description":
"Whether to clean directory first (deletes all files in directory)",
"javaType": "boolean", "type": "boolean" }, { "names": "--dir,--directory",
"description": "Directory relative path where the new Camel integration will be
saved", "defaultValue": ".", "javaType" [...]
{ "name": "jolokia", "fullName": "jolokia", "description": "Attach Jolokia
JVM Agent to a running Camel integration", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.process.Jolokia", "options": [ {
"names": "--port", "description": "To use a specific port number when attaching
Jolokia JVM Agent (default a free port is found in range 8778-9999)",
"javaType": "int", "type": "integer" }, { "names": "--stop", "description":
"Stops the Jolokia JVM Agent in the running Camel inte [...]
{ "name": "log", "fullName": "log", "description": "Tail logs from running
Camel integrations", "sourceClass":
"org.apache.camel.dsl.jbang.core.commands.action.CamelLogAction", "options": [
{ "names": "--find", "description": "Find and highlight matching text (ignore
case).", "javaType": "java.lang.String", "type": "string" }, { "names":
"--follow", "description": "Keep following and outputting new log lines (press
enter to exit).", "defaultValue": "true", "javaType": "boolean", "typ [...]
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRestart.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRestart.java
index 305a83ccbbe2..883ff82ff8e5 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRestart.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRestart.java
@@ -18,6 +18,7 @@ package org.apache.camel.dsl.jbang.core.commands.infra;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -48,6 +49,10 @@ public class InfraRestart extends InfraBaseCommand {
@CommandLine.Option(names = { "--background" }, defaultValue = "false",
description = "Run in the background")
boolean background;
+ @CommandLine.Option(names = { "--prop", "--property" },
+ description = "Service properties, ex.
--property=ollama.model=qwen2.5:0.5b")
+ List<String> serviceProperties = new ArrayList<>();
+
@CommandLine.Option(names = { "--kill" },
description = "To force killing the process (SIGKILL)
when stopping")
boolean kill;
@@ -93,6 +98,8 @@ public class InfraRestart extends InfraBaseCommand {
infraRun.port = port;
infraRun.setLogToStdout(logToStdout);
infraRun.background = background;
+ // a restart starts a new service, so it takes its properties the way
it takes its port
+ infraRun.serviceProperties = serviceProperties;
return infraRun.doCall();
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRun.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRun.java
index 993284c2e2b0..18b2e3390ffb 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRun.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRun.java
@@ -24,6 +24,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
@@ -47,7 +48,8 @@ import static
org.apache.camel.dsl.jbang.core.commands.RunHelper.addCamelCLIComm
footer = {
"%nExamples:",
" camel infra run kafka",
- " camel infra run kafka --background" })
+ " camel infra run kafka --background",
+ " camel infra run ollama --property
ollama.model=qwen2.5:0.5b" })
public class InfraRun extends InfraBaseCommand {
@CommandLine.Spec
@@ -71,6 +73,10 @@ public class InfraRun extends InfraBaseCommand {
description = "Do not start companion UI containers")
boolean noUi;
+ @CommandLine.Option(names = { "--prop", "--property" },
+ description = "Service properties, ex.
--property=ollama.model=qwen2.5:0.5b")
+ List<String> serviceProperties = new ArrayList<>();
+
public InfraRun(CamelJBangMain main) {
super(main);
}
@@ -118,10 +124,20 @@ public class InfraRun extends InfraBaseCommand {
return 1;
}
+ Map<String, String> parsedProperties;
+ try {
+ parsedProperties = parseServiceProperties();
+ } catch (IllegalArgumentException e) {
+ // before the background branch, so that a run in the background
fails here and not in a process
+ // whose only trace is its pid
+ printer().printErr(e.getMessage());
+ return 1;
+ }
+
if (background) {
return runBackground(testService);
} else {
- return doRun(testService, testServiceImplementation,
testInfraService);
+ return doRun(testService, testServiceImplementation,
testInfraService, parsedProperties);
}
}
@@ -146,6 +162,9 @@ public class InfraRun extends InfraBaseCommand {
if (noUi) {
cmds.add("--no-ui");
}
+ for (String property : serviceProperties) {
+ cmds.add("--property=" + property);
+ }
}
cmds.remove("--background=true");
@@ -162,7 +181,9 @@ public class InfraRun extends InfraBaseCommand {
return 0;
}
- protected Integer doRun(String testService, String
testServiceImplementation, TestInfraService testInfraService)
+ protected Integer doRun(
+ String testService, String testServiceImplementation,
TestInfraService testInfraService,
+ Map<String, String> parsedProperties)
throws Exception {
DependencyDownloaderClassLoader cl =
getDependencyDownloaderClassLoader(testInfraService, printer());
@@ -181,6 +202,25 @@ public class InfraRun extends InfraBaseCommand {
if (noUi) {
System.setProperty("camel.infra.ui", "false");
}
+ // the service resolves its own properties from the system properties
first, so they must be set
+ // before it is instantiated
+ Map<String, String> replacedProperties =
applyServiceProperties(parsedProperties);
+ try {
+ return startService(testService, testServiceImplementation,
testInfraService, cl, serviceInterface,
+ serviceImpl, replacedProperties);
+ } finally {
+ // the service may never have started, so this and not only the
shutdown is where a JVM that keeps
+ // running, such as the TUI or a test, gets its properties back
+ restoreProperties(replacedProperties);
+ clearInfraProperties();
+ }
+ }
+
+ private Integer startService(
+ String testService, String testServiceImplementation,
TestInfraService testInfraService,
+ DependencyDownloaderClassLoader cl, String serviceInterface,
String serviceImpl,
+ Map<String, String> replacedProperties)
+ throws Exception {
Object actualService = cl.loadClass(serviceImpl).newInstance();
// Make sure the actualService can be run with initialize method
@@ -260,7 +300,8 @@ public class InfraRun extends InfraBaseCommand {
AtomicBoolean closed = new AtomicBoolean();
// use shutdown hook as fallback to shut-down and delete files
- Runtime.getRuntime().addShutdownHook(new Thread(() ->
shutdownInfra(closed, logFile, jsonFile, actualService)));
+ Runtime.getRuntime()
+ .addShutdownHook(new Thread(() -> shutdownInfra(closed,
logFile, jsonFile, actualService, replacedProperties)));
final CountDownLatch latch = new CountDownLatch(1);
@@ -309,12 +350,63 @@ public class InfraRun extends InfraBaseCommand {
// ignore
}
- shutdownInfra(closed, logFile, jsonFile, actualService);
+ shutdownInfra(closed, logFile, jsonFile, actualService,
replacedProperties);
return 0;
}
- private static void shutdownInfra(AtomicBoolean closed, Path logFile, Path
jsonFile, Object actualService) {
+ /**
+ * Reads the --property options, which is how a service takes a setting
the CLI has no flag of its own for, such as
+ * the model of ollama. Only the pairs are read here, so that a malformed
one is refused before anything is set and
+ * before a run in the background leaves the complaint in a process nobody
is watching.
+ *
+ * @throws IllegalArgumentException if an option is not a key=value pair
+ */
+ Map<String, String> parseServiceProperties() {
+ Map<String, String> parsed = new LinkedHashMap<>();
+ for (String property : serviceProperties) {
+ int separator = property.indexOf('=');
+ if (separator < 1 || separator == property.length() - 1) {
+ throw new IllegalArgumentException(
+ "Property " + property + " is not in the key=value
form, for example ollama.model=qwen2.5:0.5b");
+ }
+ parsed.put(property.substring(0, separator),
property.substring(separator + 1));
+ }
+ return parsed;
+ }
+
+ /**
+ * Sets the properties the service resolves, and returns what they held
before, so that a JVM which runs more than
+ * one service keeps the values it was started with.
+ */
+ static Map<String, String> applyServiceProperties(Map<String, String>
parsedProperties) {
+ Map<String, String> replaced = new LinkedHashMap<>();
+ parsedProperties.forEach((name, value) -> {
+ replaced.put(name, System.getProperty(name));
+ System.setProperty(name, value);
+ });
+ return replaced;
+ }
+
+ static void restoreProperties(Map<String, String> replacedProperties) {
+ replacedProperties.forEach((name, previous) -> {
+ if (previous == null) {
+ System.clearProperty(name);
+ } else {
+ System.setProperty(name, previous);
+ }
+ });
+ }
+
+ private static void clearInfraProperties() {
+ System.clearProperty("camel.infra.port");
+ System.clearProperty("camel.infra.fixedPort");
+ System.clearProperty("camel.infra.ui");
+ }
+
+ private static void shutdownInfra(
+ AtomicBoolean closed, Path logFile, Path jsonFile, Object
actualService,
+ Map<String, String> replacedProperties) {
if (closed.compareAndSet(false, true)) {
try {
actualService.getClass().getMethod("shutdown").invoke(actualService);
@@ -331,9 +423,8 @@ public class InfraRun extends InfraBaseCommand {
} catch (Exception e) {
// ignore
}
- System.clearProperty("camel.infra.port");
- System.clearProperty("camel.infra.fixedPort");
- System.clearProperty("camel.infra.ui");
+ clearInfraProperties();
+ restoreProperties(replacedProperties);
}
}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRunPropertyTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRunPropertyTest.java
new file mode 100644
index 000000000000..3117c52b2657
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/infra/InfraRunPropertyTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.dsl.jbang.core.commands.infra;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelCommandBaseTestSupport;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.assertj.core.api.Assertions.entry;
+
+/**
+ * The --property options of camel infra run, which a service reads as system
properties.
+ */
+public class InfraRunPropertyTest extends CamelCommandBaseTestSupport {
+
+ @Test
+ public void propertiesArePairsInTheOrderTheyWereGiven() {
+ assertThat(infraRun("ollama.model=qwen2.5:0.5b",
"ollama.container.enable.gpu=enabled")
+ .parseServiceProperties())
+ .containsExactly(
+ entry("ollama.model", "qwen2.5:0.5b"),
+ entry("ollama.container.enable.gpu", "enabled"));
+ }
+
+ @Test
+ public void onlyTheFirstSeparatorSplitsTheProperty() {
+
assertThat(infraRun("ollama.model=library/qwen2.5:0.5b=latest").parseServiceProperties())
+ .containsExactly(entry("ollama.model",
"library/qwen2.5:0.5b=latest"));
+ }
+
+ @Test
+ public void aPropertyWithoutANameOrAValueIsRejected() {
+ assertThat(List.of("ollama.model", "=qwen2.5:0.5b", "ollama.model="))
+ .allSatisfy(property -> assertThatThrownBy(() ->
infraRun(property).parseServiceProperties())
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("key=value"));
+ }
+
+ @Test
+ public void
aMalformedPropertyIsRefusedBeforeTheServiceIsStartedInTheBackground() throws
Exception {
+ InfraRun infraRun = infraRun("ollama.model");
+ infraRun.setServiceName(List.of("ollama"));
+ infraRun.background = true;
+
+ assertThat(infraRun.doCall()).isEqualTo(1);
+ assertThat(printer.getOutput()).contains("key=value");
+ }
+
+ @Test
+ public void theValueTheJvmWasStartedWithComesBack() {
+ System.setProperty("ollama.model", "granite4:3b");
+ try {
+ Map<String, String> replaced =
InfraRun.applyServiceProperties(Map.of("ollama.model", "qwen2.5:0.5b"));
+
assertThat(System.getProperty("ollama.model")).isEqualTo("qwen2.5:0.5b");
+
+ InfraRun.restoreProperties(replaced);
+
+
assertThat(System.getProperty("ollama.model")).isEqualTo("granite4:3b");
+ } finally {
+ System.clearProperty("ollama.model");
+ }
+ }
+
+ @Test
+ public void aPropertyTheJvmDidNotHaveIsRemovedAgain() {
+ Map<String, String> replaced =
InfraRun.applyServiceProperties(Map.of("ollama.embedding.model", "all-minilm"));
+
assertThat(System.getProperty("ollama.embedding.model")).isEqualTo("all-minilm");
+
+ InfraRun.restoreProperties(replaced);
+
+ assertThat(System.getProperty("ollama.embedding.model")).isNull();
+ }
+
+ private InfraRun infraRun(String... properties) {
+ InfraRun infraRun = new InfraRun(new
CamelJBangMain().withPrinter(printer));
+ infraRun.serviceProperties = List.of(properties);
+ return infraRun;
+ }
+}