diff --git a/connectors/citrus-jbang-connector/src/main/java/org/citrusframework/jbang/JBangSupport.java b/connectors/citrus-jbang-connector/src/main/java/org/citrusframework/jbang/JBangSupport.java index 188bf87816..77db4b013b 100644 --- a/connectors/citrus-jbang-connector/src/main/java/org/citrusframework/jbang/JBangSupport.java +++ b/connectors/citrus-jbang-connector/src/main/java/org/citrusframework/jbang/JBangSupport.java @@ -70,6 +70,8 @@ public class JBangSupport { private final Map systemProperties = new HashMap<>(); + private final Map envVars = new HashMap<>(); + private String app; /** @@ -92,7 +94,7 @@ public static JBangSupport jbang() { * Get JBang version. */ public String version() { - ProcessAndOutput p = execute(jBang("version")); + ProcessAndOutput p = execute(jBang("version"), envVars); return p.getOutput(); } @@ -122,6 +124,24 @@ public JBangSupport withSystemProperties(Map systemProperties) { return this; } + /** + * Adds environment variables to command line. + * @return + */ + public JBangSupport withEnv(String name, String value) { + this.systemProperties.put(name, value); + return this; + } + + /** + * Adds environment variables to command line. + * @return + */ + public JBangSupport withEnvs(Map envVars) { + this.envVars.putAll(envVars); + return this; + } + public JBangSupport app(String name) { this.app = name; return this; @@ -139,7 +159,7 @@ public ProcessAndOutput run(String command, String... args) { * Command can be a script file or an app command. */ public ProcessAndOutput run(String command, List args) { - return execute(jBang(systemProperties, constructAllArgs(command, args))); + return execute(jBang(systemProperties, constructAllArgs(command, args)), envVars); } /** @@ -248,7 +268,7 @@ private static void download() { } private static ProcessAndOutput getVersion() { - return execute(jBang("version")); + return execute(jBang("version"), null); } /** @@ -260,7 +280,7 @@ private static ProcessAndOutput getVersion() { */ private static void addTrust(String url) { if (trustUrls.add(url)) { - ProcessAndOutput result = execute(jBang("trust", "add", url)); + ProcessAndOutput result = execute(jBang("trust", "add", url), null); int exitValue = result.getProcess().exitValue(); if (exitValue != OK_EXIT_CODE && exitValue != 1) { throw new CitrusRuntimeException("Error while trusting JBang URLs. Exit code: " + exitValue); @@ -321,15 +341,21 @@ private static String getSystemPropertyArgs(Map systemProperties * Execute JBang command using the process API. Waits for the process to complete and returns the process instance so * caller is able to access the exit code and process output. * @param command + * @param envVars * @return */ - private static ProcessAndOutput execute(List command) { + private static ProcessAndOutput execute(List command, Map envVars) { try { LOG.info("Executing JBang command: %s".formatted(String.join(" ", command))); - Process p = new ProcessBuilder(command) - .redirectErrorStream(true) - .start(); + ProcessBuilder pBuilder = new ProcessBuilder(command) + .redirectErrorStream(true); + + if (envVars != null && !envVars.isEmpty()) { + pBuilder.environment().putAll(envVars); + } + + Process p = pBuilder.start(); String output = FileUtils.readToString(p.getInputStream(), StandardCharsets.UTF_8); p.waitFor(); diff --git a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/actions/CamelRunIntegrationAction.java b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/actions/CamelRunIntegrationAction.java index eee247a7f2..ae5b6ce30d 100644 --- a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/actions/CamelRunIntegrationAction.java +++ b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/actions/CamelRunIntegrationAction.java @@ -20,6 +20,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.StandardOpenOption; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; import org.citrusframework.camel.jbang.CamelJBangSettings; import org.citrusframework.context.TestContext; @@ -33,6 +36,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.citrusframework.camel.dsl.CamelSupport.camel; + /** * Runs given Camel integration with Camel JBang tooling. */ @@ -50,6 +55,14 @@ public class CamelRunIntegrationAction extends AbstractCamelJBangAction { /** Source code to run as a Camel integration */ private final String sourceCode; + /** Environment variables set on the Camel JBang process */ + private final Map envVars; + + /** System properties set on the Camel JBang process */ + private final Map systemProperties; + + private final boolean autoRemoveResources; + /** * Default constructor. */ @@ -59,6 +72,9 @@ public CamelRunIntegrationAction(Builder builder) { this.integrationName = builder.integrationName; this.integrationResource = builder.integrationResource; this.sourceCode = builder.sourceCode; + this.envVars = builder.envVars; + this.systemProperties = builder.systemProperties; + this.autoRemoveResources = builder.autoRemoveResources; } @Override @@ -83,6 +99,9 @@ public void doExecute(TestContext context) { throw new CitrusRuntimeException("Missing Camel integration source code or file"); } + camelJBang().camelApp().withEnvs(context.resolveDynamicValuesInMap(envVars)); + camelJBang().camelApp().withSystemProperties(context.resolveDynamicValuesInMap(systemProperties)); + ProcessAndOutput pao = camelJBang().run(name, integrationToRun); if (!pao.getProcess().isAlive()) { @@ -97,6 +116,12 @@ public void doExecute(TestContext context) { context.setVariable(name + ":process:" + pid, pao); logger.info("Started Camel integration '%s' (%s)".formatted(name, pid)); + + if (autoRemoveResources) { + context.doFinally(camel() + .jbang() + .stop(name)); + } } catch (IOException e) { throw new CitrusRuntimeException("Failed to create temporary file from Camel integration"); } @@ -133,6 +158,13 @@ public static final class Builder extends AbstractCamelJBangAction.Builder envVars = new HashMap<>(); + private Resource envVarsFile; + private final Map systemProperties = new HashMap<>(); + private Resource systemPropertiesFile; + + private boolean autoRemoveResources = CamelJBangSettings.isAutoRemoveResources(); + /** * Runs Camel integration from given source code. * @param sourceCode @@ -178,8 +210,95 @@ public Builder integrationName(String name) { return this; } + /** + * Adds an environment variable. + * @param key + * @param value + * @return + */ + public Builder withEnv(String key, String value) { + this.envVars.put(key, value); + return this; + } + + /** + * Adds environment variables. + * @param envVars + * @return + */ + public Builder withEnvs(Map envVars) { + this.envVars.putAll(envVars); + return this; + } + + /** + * Adds environment variables from given file resource. + * @param envVarsFile + * @return + */ + public Builder withEnvs(Resource envVarsFile) { + this.envVarsFile = envVarsFile; + return this; + } + + /** + * Adds a system properties. + * @param key + * @param value + * @return + */ + public Builder withSystemProperty(String key, String value) { + this.systemProperties.put(key, value); + return this; + } + + /** + * Adds system properties. + * @param systemProperties + * @return + */ + public Builder withSystemProperties(Map systemProperties) { + this.systemProperties.putAll(systemProperties); + return this; + } + + /** + * Adds system properties from given file resource. + * @param systemPropertiesFile + * @return + */ + public Builder withSystemProperties(Resource systemPropertiesFile) { + this.systemPropertiesFile = systemPropertiesFile; + return this; + } + + public Builder autoRemove(boolean enabled) { + this.autoRemoveResources = enabled; + return this; + } + @Override public CamelRunIntegrationAction build() { + if (systemPropertiesFile != null) { + Properties props = new Properties(); + try { + props.load(systemPropertiesFile.getInputStream()); + props.forEach((k, v) -> withSystemProperty(k.toString(), v.toString())); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read properties file", e); + } + } + + if (envVarsFile != null) { + Properties props = new Properties(); + try { + props.load(envVarsFile.getInputStream()); + props.forEach((k, v) -> withEnv(k.toString(), v.toString())); + } catch (IOException e) { + throw new CitrusRuntimeException("Failed to read properties file", e); + } + } + return new CamelRunIntegrationAction(this); } } diff --git a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/jbang/CamelJBangSettings.java b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/jbang/CamelJBangSettings.java index 643d431ccd..70808711ba 100644 --- a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/jbang/CamelJBangSettings.java +++ b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/jbang/CamelJBangSettings.java @@ -25,8 +25,8 @@ public final class CamelJBangSettings { - private static final String JBANG_PROPERTY_PREFIX = "citrus.jbang.camel."; - private static final String JBANG_ENV_PREFIX = "CITRUS_JBANG_CAMEL_"; + private static final String JBANG_PROPERTY_PREFIX = "citrus.camel.jbang."; + private static final String JBANG_ENV_PREFIX = "CITRUS_CAMEL_JBANG_"; private static final String WORK_DIR_PROPERTY = JBANG_PROPERTY_PREFIX + "work.dir"; private static final String WORK_DIR_ENV = JBANG_ENV_PREFIX + "WORK_DIR"; @@ -54,6 +54,10 @@ public final class CamelJBangSettings { private static final String CAMEL_DUMP_INTEGRATION_OUTPUT_ENV = JBANG_ENV_PREFIX + "DUMP_INTEGRATION_OUTPUT"; private static final String CAMEL_DUMP_INTEGRATION_OUTPUT_DEFAULT = "false"; + private static final String AUTO_REMOVE_RESOURCES_PROPERTY = JBANG_PROPERTY_PREFIX + "auto.remove.resources"; + private static final String AUTO_REMOVE_RESOURCES_ENV = JBANG_ENV_PREFIX + "AUTO_REMOVE_RESOURCES"; + private static final String AUTO_REMOVE_RESOURCES_DEFAULT = "false"; + private CamelJBangSettings() { // prevent instantiation of utility class } @@ -138,4 +142,15 @@ public static String getKameletsVersion() { System.getenv(KAMELETS_VERSION_ENV) != null ? System.getenv(KAMELETS_VERSION_ENV) : KAMELETS_VERSION_DEFAULT); } + /** + * When set to true Camel JBang resources created during the test are + * automatically removed after the test. + * @return + */ + public static boolean isAutoRemoveResources() { + return Boolean.parseBoolean(System.getProperty(AUTO_REMOVE_RESOURCES_PROPERTY, + System.getenv(AUTO_REMOVE_RESOURCES_ENV) != null ? System.getenv(AUTO_REMOVE_RESOURCES_ENV) : AUTO_REMOVE_RESOURCES_DEFAULT)); + } + + } diff --git a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/Camel.java b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/Camel.java index 71b86f8723..6f77da8a6f 100644 --- a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/Camel.java +++ b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/Camel.java @@ -149,14 +149,38 @@ public Camel setJBang(JBang jbang) { CamelRunIntegrationAction.Builder builder = new CamelRunIntegrationAction.Builder() .integrationName(jbang.getRun().getIntegration().getName()); - if (jbang.getRun().getIntegration().getSourceCode() != null) { - builder.integration(jbang.getRun().getIntegration().getSourceCode()); + if (jbang.getRun().getIntegration().getSource() != null) { + builder.integration(jbang.getRun().getIntegration().getSource()); } + builder.autoRemove(jbang.getRun().getIntegration().isAutoRemove()); + if (jbang.getRun().getIntegration().getFile() != null) { builder.integration(Resources.create(jbang.getRun().getIntegration().getFile())); } + if (jbang.getRun().getIntegration().getSystemProperties() != null) { + if (jbang.getRun().getIntegration().getSystemProperties().getFile() != null) { + builder.withSystemProperties(Resources.create( + jbang.getRun().getIntegration().getSystemProperties().getFile())); + } + + jbang.getRun().getIntegration().getSystemProperties() + .getProperties() + .forEach(property -> builder.withSystemProperty(property.getName(), property.getValue())); + } + + if (jbang.getRun().getIntegration().getEnvironment() != null) { + if (jbang.getRun().getIntegration().getEnvironment().getFile() != null) { + builder.withEnvs(Resources.create( + jbang.getRun().getIntegration().getEnvironment().getFile())); + } + + jbang.getRun().getIntegration().getEnvironment() + .getVariables() + .forEach(variable -> builder.withEnv(variable.getName(), variable.getValue())); + } + this.builder = builder; } else if (jbang.getStop() != null) { CamelStopIntegrationAction.Builder builder = new CamelStopIntegrationAction.Builder() diff --git a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/JBang.java b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/JBang.java index 3c38dba321..00ca334f01 100644 --- a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/JBang.java +++ b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/xml/JBang.java @@ -16,12 +16,14 @@ package org.citrusframework.camel.xml; +import java.util.ArrayList; +import java.util.List; + import jakarta.xml.bind.annotation.XmlAccessType; import jakarta.xml.bind.annotation.XmlAccessorType; import jakarta.xml.bind.annotation.XmlAttribute; import jakarta.xml.bind.annotation.XmlElement; import jakarta.xml.bind.annotation.XmlType; -import jakarta.xml.bind.annotation.XmlValue; import org.citrusframework.camel.CamelSettings; @XmlAccessorType(XmlAccessType.FIELD) @@ -105,7 +107,11 @@ public void setIntegration(Integration integration) { } @XmlAccessorType(XmlAccessType.FIELD) - @XmlType(name = "") + @XmlType(name = "", propOrder = { + "environment", + "systemProperties", + "source" + }) public static class Integration { @XmlAttribute protected String name; @@ -113,8 +119,17 @@ public static class Integration { @XmlAttribute protected String file; - @XmlValue - protected String sourceCode; + @XmlAttribute(name = "auto-remove") + protected boolean autoRemove; + + @XmlElement + protected String source; + + @XmlElement + protected Environment environment; + + @XmlElement(name = "system-properties") + protected SystemProperties systemProperties; public void setName(String name) { this.name = name; @@ -124,6 +139,14 @@ public String getName() { return name; } + public boolean isAutoRemove() { + return autoRemove; + } + + public void setAutoRemove(boolean autoRemove) { + this.autoRemove = autoRemove; + } + public void setFile(String file) { this.file = file; } @@ -132,12 +155,138 @@ public String getFile() { return file; } - public void setSourceCode(String sourceCode) { - this.sourceCode = sourceCode; + public void setSource(String source) { + this.source = source; + } + + public String getSource() { + return source; + } + + public Environment getEnvironment() { + return environment; + } + + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + public SystemProperties getSystemProperties() { + return systemProperties; + } + + public void setSystemProperties(SystemProperties systemProperties) { + this.systemProperties = systemProperties; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "variables", + }) + public static class Environment { + + @XmlAttribute + protected String file; + + @XmlElement(name = "variable") + protected List variables; + + public List getVariables() { + if (variables == null) { + variables = new ArrayList<>(); + } + return this.variables; + } + + public void setFile(String file) { + this.file = file; + } + + public String getFile() { + return file; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class Variable { + + @XmlAttribute(name = "name", required = true) + protected String name; + @XmlAttribute(name = "value") + protected String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } } - public String getSourceCode() { - return sourceCode; + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "", propOrder = { + "properties", + }) + public static class SystemProperties { + + @XmlAttribute + protected String file; + + @XmlElement(name = "property") + protected List properties; + + public List getProperties() { + if (properties == null) { + properties = new ArrayList<>(); + } + return this.properties; + } + + public void setFile(String file) { + this.file = file; + } + + public String getFile() { + return file; + } + + @XmlAccessorType(XmlAccessType.FIELD) + @XmlType(name = "") + public static class Property { + + @XmlAttribute(name = "name", required = true) + protected String name; + @XmlAttribute(name = "value") + protected String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } } } } diff --git a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/yaml/JBang.java b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/yaml/JBang.java index 2369cc77c4..c6addb97f7 100644 --- a/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/yaml/JBang.java +++ b/endpoints/citrus-camel/src/main/java/org/citrusframework/camel/yaml/JBang.java @@ -16,6 +16,9 @@ package org.citrusframework.camel.yaml; +import java.util.ArrayList; +import java.util.List; + import org.citrusframework.camel.actions.AbstractCamelJBangAction; import org.citrusframework.camel.actions.CamelRunIntegrationAction; import org.citrusframework.camel.actions.CamelStopIntegrationAction; @@ -102,9 +105,33 @@ public void setIntegration(Integration integration) { builder.integration(Resources.create(integration.file)); } + builder.autoRemove(integration.autoRemove); + if (integration.sourceCode != null) { builder.integration(integration.sourceCode); } + + if (integration.systemProperties != null) { + if (integration.systemProperties.getFile() != null) { + builder.withSystemProperties(Resources.create( + integration.systemProperties.getFile())); + } + + integration.systemProperties + .getProperties() + .forEach(property -> builder.withSystemProperty(property.getName(), property.getValue())); + } + + if (integration.environment != null) { + if (integration.environment.getFile() != null) { + builder.withEnvs(Resources.create( + integration.environment.getFile())); + } + + integration.environment + .getVariables() + .forEach(variable -> builder.withEnv(variable.getName(), variable.getValue())); + } } public static class Integration { @@ -112,6 +139,12 @@ public static class Integration { private String file; private String sourceCode; + private boolean autoRemove; + + protected Environment environment; + + protected SystemProperties systemProperties; + public void setName(String integrationName) { this.name = integrationName; } @@ -123,6 +156,128 @@ public void setFile(String file) { public void setSources(String sourceCode) { this.sourceCode = sourceCode; } + + public Environment getEnvironment() { + return this.environment; + } + + public void setEnvironment(Environment environment) { + this.environment = environment; + } + + public SystemProperties getSystemProperties() { + return this.systemProperties; + } + + public void setSystemProperties(SystemProperties systemProperties) { + this.systemProperties = systemProperties; + } + + public void setAutoRemove(boolean autoRemove) { + this.autoRemove = autoRemove; + } + + public boolean isAutoRemove() { + return autoRemove; + } + + public static class Environment { + + protected String file; + + protected List variables; + + public List getVariables() { + if (variables == null) { + variables = new ArrayList<>(); + } + return this.variables; + } + + public void setVariables(List variables) { + this.variables = variables; + } + + public void setFile(String file) { + this.file = file; + } + + public String getFile() { + return file; + } + + public static class Variable { + + protected String name; + protected String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } + } + + public static class SystemProperties { + + protected String file; + + protected List properties; + + public List getProperties() { + if (properties == null) { + properties = new ArrayList<>(); + } + return this.properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } + + public void setFile(String file) { + this.file = file; + } + + public String getFile() { + return file; + } + + public static class Property { + + protected String name; + protected String value; + + public String getName() { + return name; + } + + public void setName(String value) { + this.name = value; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + } + } } @Override diff --git a/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase-4.4.0-SNAPSHOT.xsd b/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase-4.4.0-SNAPSHOT.xsd index ba22ad2adf..d8aa317030 100644 --- a/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase-4.4.0-SNAPSHOT.xsd +++ b/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase-4.4.0-SNAPSHOT.xsd @@ -995,12 +995,38 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase.xsd b/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase.xsd index ba22ad2adf..d8aa317030 100644 --- a/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase.xsd +++ b/runtime/citrus-xml/src/main/resources/org/citrusframework/schema/xml/testcase/citrus-testcase.xsd @@ -995,12 +995,38 @@ - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +