This is an automated email from the ASF dual-hosted git repository.
gfournier 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 a1d8818cf9d CAMEL-20964: Add property based trait configuration in
camel-jbang kubernetes plugin
a1d8818cf9d is described below
commit a1d8818cf9de12a47dba2818aa92b969a6c22102
Author: Gaelle Fournier <[email protected]>
AuthorDate: Thu Jul 25 16:47:01 2024 +0200
CAMEL-20964: Add property based trait configuration in camel-jbang
kubernetes plugin
* support properties traits configuration in application.properties
* support profile properties trait configuration in
application-{profile].propertie
* define override order in trait configuration
---
.../modules/ROOT/pages/camel-jbang-kubernetes.adoc | 10 ++
.../modules/ROOT/pages/camel-jbang.adoc | 1 +
.../dsl/jbang/core/commands/ExportBaseCommand.java | 2 +-
.../jbang/core/commands/k/IntegrationExport.java | 4 +-
.../core/commands/kubernetes/KubernetesExport.java | 40 ++++++--
.../commands/kubernetes/traits/TraitHelper.java | 98 ++++++++++++++-----
.../kubernetes/traits/TraitHelperTest.java | 107 +++++++++++++++++++++
7 files changed, 228 insertions(+), 34 deletions(-)
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
index 3e25175e420..59e5bb0e3cf 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
@@ -150,6 +150,9 @@ The Camel JBang Kubernetes export command provides several
options to customize
|--cluster-type
|The target cluster type. Special configurations may be applied to different
cluster types such as Kind or Minikube.
+
+|--profile
+|The developer xref:manual::camel-jbang.adoc#_using_profiles[profile] to use a
specific configuration in configuration files using the naming style
`application-<profile>.properties`.
|=======================================================================
== Kubernetes manifest options
@@ -161,6 +164,13 @@ You can use several options on the `export` command to
customize this manifest w
The trait concept was born out of Camel K and the Camel K operator uses the
traits to configure the Kubernetes resources that are managed by an integration.
You can use the same options to also customize the Kubernetes manifest that is
generated as part of the project export.
+The configuration of the traits are used by the given order:
+
+1. Use the `--trait` command options values
+2. Any annotation starting with the prefix `trait.camel.apache.org/*`
+3. Any properties from the specific configuration
`application-<profile>.properties` for the profile defined by the command
option `--profile` with the prefix `camel.jbang.trait.*`
+4. Any properties from the default configuration `application.properties` with
the prefix `camel.jbang.trait.*`
+
=== Container trait options
The container specification is part of the Kubernetes Deployment resource and
describes the application container image, exposed ports and health probes for
example.
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
index 53ca7665587..452bd843d67 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang.adoc
@@ -547,6 +547,7 @@ And if you have `jq` installed which can format and output
the JSon data in colo
curl -s -H "Accept: application/json" http://0.0.0.0:8080/q/dev/top/ | jq
----
+[#_using_profiles]
=== Using profiles
*Available from Camel 4.5*
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
index c2bd7b30a22..14094d44283 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ExportBaseCommand.java
@@ -141,7 +141,7 @@ public abstract class ExportBaseCommand extends
CamelCommand {
@CommandLine.Option(names = { "--profile" }, scope =
CommandLine.ScopeType.INHERIT,
description = "Profile to export (dev, test, or
prod).")
- String profile;
+ protected String profile;
@CommandLine.Option(names = { "--local-kamelet-dir" },
description = "Local directory for loading Kamelets
(takes precedence)")
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java
index 247e4889a5d..4e8f41f681f 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java
@@ -100,7 +100,7 @@ public class IntegrationExport extends KubernetesExport {
}
@Override
- protected Traits getTraitSpec() {
+ protected Traits getTraitSpec(String[] applicationProperties, String[]
applicationProfileProperties) {
if (integration != null && integration.getSpec().getTraits() != null) {
return integration.getSpec().getTraits();
}
@@ -112,7 +112,7 @@ public class IntegrationExport extends KubernetesExport {
.loadAs(KubernetesHelper.dumpYaml(pipe.getSpec().getIntegration().getTraits()),
Traits.class);
}
- return super.getTraitSpec();
+ return super.getTraitSpec(applicationProperties,
applicationProfileProperties);
}
@Override
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
index 039b767591a..6f3565d8850 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExport.java
@@ -26,6 +26,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.Properties;
import java.util.stream.Collectors;
import org.apache.camel.catalog.CamelCatalog;
@@ -40,8 +41,10 @@ import
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitHelper;
import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitProfile;
import org.apache.camel.dsl.jbang.core.common.CommandLineHelper;
import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+import org.apache.camel.dsl.jbang.core.common.RuntimeUtil;
import org.apache.camel.dsl.jbang.core.common.Source;
import org.apache.camel.dsl.jbang.core.common.SourceHelper;
+import org.apache.camel.util.CamelCaseOrderedProperties;
import org.apache.camel.util.StringHelper;
import org.apache.camel.v1.integrationspec.Traits;
import org.apache.camel.v1.integrationspec.traits.Container;
@@ -219,7 +222,17 @@ public class KubernetesExport extends Export {
context.setServiceAccount(serviceAccount);
}
- Traits traitsSpec = getTraitSpec();
+ // application.properties
+ String[] applicationProperties = extractPropertiesTraits(new
File("application.properties"));
+
+ // application-{profile}.properties
+ String[] applicationProfileProperties = null;
+ if (this.profile != null) {
+ // override from profile specific configuration
+ applicationProfileProperties = extractPropertiesTraits(new
File("application-" + profile + ".properties"));
+ }
+
+ Traits traitsSpec = getTraitSpec(applicationProperties,
applicationProfileProperties);
TraitHelper.configureMountTrait(traitsSpec, configs, resources,
volumes);
if (openapi != null && openapi.startsWith("configmap:")) {
@@ -363,12 +376,17 @@ public class KubernetesExport extends Export {
return super.export(cmd);
}
- protected Traits getTraitSpec() {
+ protected Traits getTraitSpec(String[] applicationProperties, String[]
applicationProfileProperties) {
+
+ // annotation traits
+ String[] annotationsTraits =
TraitHelper.extractTraitsFromAnnotations(this.annotations);
+
+ String[] allTraits
+ = TraitHelper.mergeTraits(traits, annotationsTraits,
applicationProfileProperties, applicationProperties);
+
Traits traitsSpec;
- if (traits != null && traits.length > 0) {
- traitsSpec = TraitHelper.parseTraits(traits, annotations);
- } else if (annotations != null && annotations.length > 0) {
- traitsSpec = TraitHelper.parseTraits(new String[0], annotations);
+ if (allTraits != null && allTraits.length > 0) {
+ traitsSpec = TraitHelper.parseTraits(allTraits);
} else {
traitsSpec = new Traits();
}
@@ -447,6 +465,16 @@ public class KubernetesExport extends Export {
return imageRegistry;
}
+ protected String[] extractPropertiesTraits(File file) throws Exception {
+ if (file.exists()) {
+ Properties prop = new CamelCaseOrderedProperties();
+ RuntimeUtil.loadProperties(prop, file);
+ return TraitHelper.extractTraitsFromProperties(prop);
+ } else {
+ return null;
+ }
+ }
+
protected String getProjectName() {
if (image != null) {
return
KubernetesHelper.sanitize(KubernetesHelper.sanitize(StringHelper.beforeLast(image,
":")));
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java
index 27a7cf805cc..11f8b90b949 100644
---
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java
+++
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelper.java
@@ -20,9 +20,11 @@ package
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Properties;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -53,40 +55,20 @@ public final class TraitHelper {
}
/**
- * Parses given list of trait expressions to proper trait model object.
+ * Parses given list of trait expressions to proper trait model object.
Supports trait options in the form of
+ * key=value.
*
+ * @param traits trait key-value-pairs.
+ * @return
*/
public static Traits parseTraits(String[] traits) {
if (traits == null || traits.length == 0) {
return new Traits();
}
- return parseTraits(traits, null);
- }
-
- /**
- * Parses given list of trait expressions to proper trait model object.
Supports trait options in the form of
- * key=value and trait annotation configuration.
- *
- * @param traits trait key-value-pairs.
- * @param annotations trait annotation configuration.
- */
- public static Traits parseTraits(String[] traits, String[] annotations) {
Map<String, Map<String, Object>> traitConfigMap = new HashMap<>();
- String[] traitExpressions;
- if (annotations != null) {
- Stream<String> annotationTraits = Stream.of(annotations)
- .filter(annotation ->
annotation.startsWith("trait.camel.apache.org/"))
- .map(annotation -> StringHelper.after(annotation,
"trait.camel.apache.org/"));
-
- traitExpressions
- = Stream.concat(Stream.of(traits),
annotationTraits).collect(Collectors.toSet()).toArray(String[]::new);
- } else {
- traitExpressions = traits;
- }
-
- for (String traitExpression : traitExpressions) {
+ for (String traitExpression : traits) {
//traitName.key=value
final String[] trait = traitExpression.split("\\.", 2);
final String[] traitConfig = trait[1].split("=", 2);
@@ -342,4 +324,70 @@ public final class TraitHelper {
return false;
}
}
+
+ /**
+ * Extract properties traits (camel.jbang.trait.key=value) and transform
them into regular trait form (key=value)
+ *
+ * @param properties properties
+ * @return traits
+ */
+ public static String[] extractTraitsFromProperties(Properties properties) {
+ if (properties != null && !properties.isEmpty()) {
+ Stream<String> propertyTraits = properties.entrySet().stream()
+ .filter(property ->
property.getKey().toString().startsWith("camel.jbang.trait"))
+ .map(property ->
StringHelper.after(property.getKey().toString(), "camel.jbang.trait.") + "="
+ +
properties.get(property.getKey()).toString());
+ return
propertyTraits.collect(Collectors.toSet()).toArray(String[]::new);
+ }
+ return new String[0];
+ }
+
+ /**
+ * Extract annotation traits (trait.camel.apache.org/key=value) and
transform them into regular trait form
+ * (key=value)
+ *
+ * @param annotations annotations
+ * @return traits
+ */
+ public static String[] extractTraitsFromAnnotations(String[] annotations) {
+ if (annotations != null && annotations.length > 0) {
+ Stream<String> annotationTraits = Stream.of(annotations)
+ .filter(annotation ->
annotation.startsWith("trait.camel.apache.org/"))
+ .map(annotation -> StringHelper.after(annotation,
"trait.camel.apache.org/"));
+ return
annotationTraits.collect(Collectors.toSet()).toArray(String[]::new);
+ }
+ return new String[0];
+ }
+
+ /**
+ * Merge all the traits from multiple sources in one keeping overrides
priority by its position in the list. A trait
+ * property value in the array 0 will have priority on the value of the
same trait property in an array 1. Supports
+ * trait options in the form of key=value.
+ *
+ * @param traitsBySource traits grouped by source
+ * @return the traits merged
+ */
+ public static String[] mergeTraits(String[]... traitsBySource) {
+ if (traitsBySource == null || traitsBySource.length == 0) {
+ return new String[0];
+ }
+ Set<String> existingKeys = new HashSet<>();
+ List<String> mergedTraits = new ArrayList<>();
+ for (String[] traits : traitsBySource) {
+ if (traits != null && traits.length > 0) {
+ for (String trait : traits) {
+ final String[] traitConfig = trait.split("=", 2);
+ if (!existingKeys.contains(traitConfig[0])) {
+ mergedTraits.add(trait);
+ }
+ }
+ existingKeys.clear();
+ for (String trait : mergedTraits) {
+ final String[] traitConfig = trait.split("=", 2);
+ existingKeys.add(traitConfig[0]);
+ }
+ }
+ }
+ return mergedTraits.toArray(new String[0]);
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelperTest.java
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelperTest.java
new file mode 100644
index 00000000000..bf21e2d62ec
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitHelperTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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.kubernetes.traits;
+
+import java.util.Arrays;
+import java.util.Properties;
+
+import org.apache.camel.v1.integrationspec.Traits;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TraitHelperTest {
+
+ @Test
+ public void mergeTraitsTest() {
+ String[] defaultGroup = new String[] {
+ "container.port=8080",
+ "container.port-name=custom",
+ "container.service-port-name=custom-port",
+ "container.service-port=8443" };
+ String[] overridesGroup = new String[] {
+ "container.port=80",
+ "container.service-port=443",
+ "container.image-pull-policy=IfNotPresent" };
+ String[] result = TraitHelper.mergeTraits(overridesGroup,
defaultGroup);
+ Assertions.assertNotNull(result);
+ Assertions.assertEquals(5, result.length);
+ Assertions.assertEquals("container.port=80", result[0]);
+ Assertions.assertEquals("container.service-port-name=custom-port",
result[4]);
+
+ String[] resultEmptyDefault = TraitHelper.mergeTraits(overridesGroup,
new String[0]);
+ Assertions.assertNotNull(resultEmptyDefault);
+ Assertions.assertEquals(3, resultEmptyDefault.length);
+ Assertions.assertArrayEquals(overridesGroup, resultEmptyDefault);
+
+ String[] resultNull = TraitHelper.mergeTraits(null);
+ Assertions.assertNotNull(resultNull);
+ }
+
+ @Test
+ public void extractTraitsFromAnnotationsTest() {
+ String[] annotations = new String[] {
+ "trait.camel.apache.org/container.port=8080",
+ "trait.camel.apache.org/container.port-name=custom",
+ "camel.apache.org/name=MyRoute" };
+ String[] result =
TraitHelper.extractTraitsFromAnnotations(annotations);
+ Assertions.assertNotNull(result);
+ Assertions.assertEquals(2, result.length);
+ Assertions.assertEquals("container.port-name=custom", result[1]);
+
+ String[] resultEmpty = TraitHelper.extractTraitsFromAnnotations(new
String[0]);
+ Assertions.assertNotNull(resultEmpty);
+
+ String[] resultNull = TraitHelper.extractTraitsFromAnnotations(null);
+ Assertions.assertNotNull(resultNull);
+ }
+
+ @Test
+ public void extractTraitsFromPropertiesTest() {
+ Properties properties = new Properties();
+ properties.setProperty("camel.jbang.trait.container.port", "8080");
+ properties.setProperty("camel.jbang.trait.container.port-name",
"custom");
+ properties.setProperty("camel.jbang.name", "MyRoute");
+ String[] result = TraitHelper.extractTraitsFromProperties(properties);
+ Assertions.assertNotNull(result);
+ Assertions.assertEquals(2, result.length);
+
Assertions.assertTrue(Arrays.asList(result).contains("container.port-name=custom"));
+
+ String[] resultEmpty = TraitHelper.extractTraitsFromProperties(new
Properties());
+ Assertions.assertNotNull(resultEmpty);
+
+ String[] resultNull = TraitHelper.extractTraitsFromProperties(null);
+ Assertions.assertNotNull(resultNull);
+ }
+
+ @Test
+ public void parseTraitsTest() {
+ String[] traits = new String[] {
+ "custom.property=custom",
+ "container.port=8080",
+ "container.port-name=custom" };
+ Traits traitsSpec = TraitHelper.parseTraits(traits);
+ Assertions.assertNotNull(traitsSpec);
+ Assertions.assertEquals(8080L, traitsSpec.getContainer().getPort());
+ Assertions.assertNotNull(traitsSpec.getAddons().get("custom"));
+
+ Traits traitsSpecEmpty = TraitHelper.parseTraits(new String[0]);
+ Assertions.assertNotNull(traitsSpecEmpty);
+
+ Traits traitsSpecNull = TraitHelper.parseTraits(null);
+ Assertions.assertNotNull(traitsSpecNull);
+ }
+}