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);
+    }
+}

Reply via email to