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

commit 08135353a081115bf4df59db49616ebd501613cb
Author: Gaelle Fournier <[email protected]>
AuthorDate: Wed Sep 25 15:50:57 2024 +0200

    CAMEL-21152: Adapt camel jbang plugin k to the modifications in camel jbang 
kubernetes plugin
---
 .../camel/dsl/jbang/core/commands/k/Bind.java      |   5 +-
 .../jbang/core/commands/k/IntegrationExport.java   |  11 +-
 .../dsl/jbang/core/commands/k/IntegrationRun.java  |  27 +-
 .../core/commands/k/IntegrationTraitHelper.java    | 314 +++++++++++++++++++++
 4 files changed, 338 insertions(+), 19 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java
 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java
index e7405bf04dc..b5a0f8fd381 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/Bind.java
@@ -27,7 +27,6 @@ import 
org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
 import org.apache.camel.dsl.jbang.core.commands.bind.TemplateProvider;
 import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesBaseCommand;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper;
-import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitHelper;
 import org.apache.camel.util.ObjectHelper;
 import org.apache.camel.v1.Pipe;
 import org.apache.camel.v1.integrationspec.Traits;
@@ -163,14 +162,14 @@ public class Bind extends KubernetesBaseCommand {
         String integrationSpec = "";
         Traits traitsSpec = null;
         if (traits != null && traits.length > 0) {
-            traitsSpec = TraitHelper.parseTraits(traits);
+            traitsSpec = IntegrationTraitHelper.parseTraits(traits);
         }
 
         if (connects != null) {
             if (traitsSpec == null) {
                 traitsSpec = new Traits();
             }
-            TraitHelper.configureConnects(traitsSpec, connects);
+            IntegrationTraitHelper.configureConnects(traitsSpec, connects);
         }
 
         if (traitsSpec != null) {
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 4e8f41f681f..7d32e93e138 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
@@ -27,7 +27,6 @@ import org.apache.camel.dsl.jbang.core.common.Source;
 import org.apache.camel.dsl.jbang.core.common.SourceHelper;
 import org.apache.camel.v1.Integration;
 import org.apache.camel.v1.Pipe;
-import org.apache.camel.v1.integrationspec.Traits;
 import picocli.CommandLine;
 
 @CommandLine.Command(name = "export",
@@ -100,16 +99,20 @@ public class IntegrationExport extends KubernetesExport {
     }
 
     @Override
-    protected Traits getTraitSpec(String[] applicationProperties, String[] 
applicationProfileProperties) {
+    protected 
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits 
getTraitSpec(
+            String[] applicationProperties, String[] 
applicationProfileProperties) {
         if (integration != null && integration.getSpec().getTraits() != null) {
-            return integration.getSpec().getTraits();
+            return KubernetesHelper.yaml(this.getClass().getClassLoader())
+                    
.loadAs(KubernetesHelper.dumpYaml(integration.getSpec().getTraits()),
+                            
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits.class);
         }
 
         if (pipe != null && pipe.getSpec().getIntegration() != null
                 && pipe.getSpec().getIntegration().getTraits() != null) {
             // convert pipe spec traits to integration spec traits
             return KubernetesHelper.yaml(this.getClass().getClassLoader())
-                    
.loadAs(KubernetesHelper.dumpYaml(pipe.getSpec().getIntegration().getTraits()), 
Traits.class);
+                    
.loadAs(KubernetesHelper.dumpYaml(pipe.getSpec().getIntegration().getTraits()),
+                            
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits.class);
         }
 
         return super.getTraitSpec(applicationProperties, 
applicationProfileProperties);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
index 1bae50309a7..45b36af7ede 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationRun.java
@@ -35,7 +35,6 @@ import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesBaseCommand
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitCatalog;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitContext;
-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.JSonHelper;
 import org.apache.camel.dsl.jbang.core.common.Printer;
@@ -244,10 +243,10 @@ public class IntegrationRun extends KubernetesBaseCommand 
{
                     .collect(Collectors.toMap(it -> it[0].trim(), it -> 
it[1].trim())));
         }
 
-        Traits traitsSpec = TraitHelper.parseTraits(traits);
+        Traits traitsSpec = IntegrationTraitHelper.parseTraits(traits);
 
         if (image != null) {
-            TraitHelper.configureContainerImage(traitsSpec, image, null, null, 
null, null);
+            IntegrationTraitHelper.configureContainerImage(traitsSpec, image, 
null, null, null, null);
         } else {
             List<Source> resolvedSources = 
SourceHelper.resolveSources(integrationSources, compression);
 
@@ -314,10 +313,14 @@ public class IntegrationRun extends KubernetesBaseCommand 
{
                     List<Source> sources = 
SourceHelper.resolveSources(integrationSources);
                     TraitContext context
                             = new 
TraitContext(integration.getMetadata().getName(), "1.0-SNAPSHOT", printer(), 
sources);
-                    TraitHelper.configureContainerImage(traitsSpec, image, 
"quay.io", null, integration.getMetadata().getName(),
+                    IntegrationTraitHelper.configureContainerImage(traitsSpec, 
image, "quay.io", null,
+                            integration.getMetadata().getName(),
                             "1.0-SNAPSHOT");
-
-                    new TraitCatalog().apply(traitsSpec, context, 
traitProfile);
+                    
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits 
kubernetesTraits
+                            = 
KubernetesHelper.yaml(this.getClass().getClassLoader())
+                                    .loadAs(KubernetesHelper.dumpYaml(traits),
+                                            
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.model.Traits.class);
+                    new TraitCatalog().apply(kubernetesTraits, context, 
traitProfile);
 
                     printer().println(
                             
context.buildItems().stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---")));
@@ -362,14 +365,14 @@ public class IntegrationRun extends KubernetesBaseCommand 
{
     }
 
     private void convertOptionsToTraits(Traits traitsSpec) {
-        TraitHelper.configureMountTrait(traitsSpec, configs, resources, 
volumes);
+        IntegrationTraitHelper.configureMountTrait(traitsSpec, configs, 
resources, volumes);
         if (openApis != null) {
-            Stream.of(openApis).forEach(openapi -> 
TraitHelper.configureOpenApiSpec(traitsSpec, openapi));
+            Stream.of(openApis).forEach(openapi -> 
IntegrationTraitHelper.configureOpenApiSpec(traitsSpec, openapi));
         }
-        TraitHelper.configureProperties(traitsSpec, properties);
-        TraitHelper.configureBuildProperties(traitsSpec, buildProperties);
-        TraitHelper.configureEnvVars(traitsSpec, envVars);
-        TraitHelper.configureConnects(traitsSpec, connects);
+        IntegrationTraitHelper.configureProperties(traitsSpec, properties);
+        IntegrationTraitHelper.configureBuildProperties(traitsSpec, 
buildProperties);
+        IntegrationTraitHelper.configureEnvVars(traitsSpec, envVars);
+        IntegrationTraitHelper.configureConnects(traitsSpec, connects);
     }
 
     private String getIntegrationName(List<String> sources) {
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationTraitHelper.java
 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationTraitHelper.java
new file mode 100644
index 00000000000..5e2817de83d
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationTraitHelper.java
@@ -0,0 +1,314 @@
+/*
+ * 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.k;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper;
+import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.ContainerTrait;
+import org.apache.camel.util.StringHelper;
+import org.apache.camel.v1.integrationspec.Traits;
+import org.apache.camel.v1.integrationspec.traits.AddonsBuilder;
+import org.apache.camel.v1.integrationspec.traits.Builder;
+import org.apache.camel.v1.integrationspec.traits.Camel;
+import org.apache.camel.v1.integrationspec.traits.Container;
+import org.apache.camel.v1.integrationspec.traits.Environment;
+import org.apache.camel.v1.integrationspec.traits.Mount;
+import org.apache.camel.v1.integrationspec.traits.Openapi;
+import org.apache.camel.v1.integrationspec.traits.ServiceBinding;
+
+public final class IntegrationTraitHelper {
+
+    private IntegrationTraitHelper() {
+        //prevent instantiation of utility class.
+    }
+
+    /**
+     * 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();
+        }
+
+        Map<String, Map<String, Object>> traitConfigMap = new HashMap<>();
+
+        for (String traitExpression : traits) {
+            //traitName.key=value
+            final String[] trait = traitExpression.split("\\.", 2);
+            final String[] traitConfig = trait[1].split("=", 2);
+
+            // the CRD api is in CamelCase, then we have to
+            // convert the kebab-case to CamelCase
+            final String traitKey = 
StringHelper.dashToCamelCase(traitConfig[0]);
+            final Object traitValue = resolveTraitValue(traitKey, 
traitConfig[1].trim());
+            if (traitConfigMap.containsKey(trait[0])) {
+                Map<String, Object> config = traitConfigMap.get(trait[0]);
+
+                if (config.containsKey(traitKey)) {
+                    Object existingValue = config.get(traitKey);
+
+                    if (existingValue instanceof List) {
+                        List<String> values = (List<String>) existingValue;
+                        if (traitValue instanceof List) {
+                            List<String> traitValueList = (List<String>) 
traitValue;
+                            values.addAll(traitValueList);
+                        } else {
+                            values.add(traitValue.toString());
+                        }
+                    } else if (existingValue instanceof Map) {
+                        Map<String, String> values = (Map<String, String>) 
existingValue;
+                        if (traitValue instanceof Map) {
+                            Map<String, String> traitValueList = (Map<String, 
String>) traitValue;
+                            values.putAll(traitValueList);
+                        } else {
+                            final String[] traitValueConfig = 
traitValue.toString().split("=", 2);
+                            values.put(traitValueConfig[0], 
traitValueConfig[1]);
+                        }
+                    } else if (traitValue instanceof List) {
+                        List<String> traitValueList = (List<String>) 
traitValue;
+                        traitValueList.add(0, existingValue.toString());
+                        config.put(traitKey, traitValueList);
+                    } else if (traitValue instanceof Map) {
+                        Map<String, String> traitValueMap = (Map<String, 
String>) traitValue;
+                        final String[] existingValueConfig = 
existingValue.toString().split("=", 2);
+                        traitValueMap.put(existingValueConfig[0], 
existingValueConfig[1]);
+                        config.put(traitKey, traitValueMap);
+                    } else {
+                        if (traitKey.endsWith("annotations")) {
+                            Map<String, String> map = new LinkedHashMap<>();
+                            final String[] traitValueConfig = 
traitValue.toString().split("=", 2);
+                            final String[] existingValueConfig = 
existingValue.toString().split("=", 2);
+                            map.put(traitValueConfig[0], traitValueConfig[1]);
+                            map.put(existingValueConfig[0], 
existingValueConfig[1]);
+                            config.put(traitKey, map);
+                        } else {
+                            config.put(traitKey, 
Arrays.asList(existingValue.toString(), traitValue));
+                        }
+                    }
+                } else {
+                    config.put(traitKey, traitValue);
+                }
+            } else {
+                Map<String, Object> config = new HashMap<>();
+                config.put(traitKey, traitValue);
+                traitConfigMap.put(trait[0], config);
+            }
+        }
+
+        Traits traitModel = 
KubernetesHelper.json().convertValue(traitConfigMap, Traits.class);
+
+        // Handle leftover traits as addons
+        Set<?> knownTraits = KubernetesHelper.json().convertValue(traitModel, 
Map.class).keySet();
+        if (knownTraits.size() < traitConfigMap.size()) {
+            traitModel.setAddons(new HashMap<>());
+            for (Map.Entry<String, Map<String, Object>> traitConfig : 
traitConfigMap.entrySet()) {
+                if (!knownTraits.contains(traitConfig.getKey())) {
+                    traitModel.getAddons().put(traitConfig.getKey(),
+                            new 
AddonsBuilder().addToAdditionalProperties(traitConfig.getValue()).build());
+                }
+            }
+        }
+
+        return traitModel;
+    }
+
+    /**
+     * Resolve trait value with automatic type conversion. Some trait keys 
(like enabled, verbose) need to be converted
+     * to boolean type.
+     *
+     */
+    private static Object resolveTraitValue(String traitKey, String value) {
+        if (traitKey.equalsIgnoreCase("enabled") ||
+                traitKey.equalsIgnoreCase("verbose")) {
+            return Boolean.valueOf(value);
+        }
+
+        if (value.startsWith("[") && value.endsWith("]")) {
+            String valueArrayExpression = value.substring(1, value.length() - 
1);
+            List<String> values = new ArrayList<>();
+            if (valueArrayExpression.contains(",")) {
+                values.addAll(List.of(valueArrayExpression.split(",")));
+            } else {
+                values.add(valueArrayExpression);
+            }
+            return values;
+        }
+
+        if (value.contains(",")) {
+            List<String> values = new ArrayList<>();
+            for (String entry : value.split(",")) {
+                values.add(resolveTraitValue("", entry).toString());
+            }
+            return values;
+        }
+
+        if (value.startsWith("\"") && value.endsWith("\"")) {
+            return value.substring(1, value.length() - 1);
+        }
+
+        if (value.startsWith("'") && value.endsWith("'")) {
+            return value.substring(1, value.length() - 1);
+        }
+
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return value;
+        }
+    }
+
+    public static void configureConnects(Traits traitsSpec, String[] connects) 
{
+        if (connects == null || connects.length == 0) {
+            return;
+        }
+        ServiceBinding serviceBindingTrait = 
Optional.ofNullable(traitsSpec.getServiceBinding()).orElseGet(ServiceBinding::new);
+        if (serviceBindingTrait.getServices() == null) {
+            serviceBindingTrait.setServices(new ArrayList<>());
+        }
+        serviceBindingTrait.getServices().addAll(List.of(connects));
+        traitsSpec.setServiceBinding(serviceBindingTrait);
+    }
+
+    public static void configureEnvVars(Traits traitsSpec, String[] envVars) {
+        if (envVars == null || envVars.length == 0) {
+            return;
+        }
+        Environment environmentTrait = 
Optional.ofNullable(traitsSpec.getEnvironment()).orElseGet(Environment::new);
+        if (environmentTrait.getVars() == null) {
+            environmentTrait.setVars(new ArrayList<>());
+        }
+        environmentTrait.getVars().addAll(List.of(envVars));
+        traitsSpec.setEnvironment(environmentTrait);
+    }
+
+    public static void configureBuildProperties(Traits traitsSpec, String[] 
buildProperties) {
+        if (buildProperties == null || buildProperties.length == 0) {
+            return;
+        }
+
+        Builder builderTrait = 
Optional.ofNullable(traitsSpec.getBuilder()).orElseGet(Builder::new);
+        if (builderTrait.getProperties() == null) {
+            builderTrait.setProperties(new ArrayList<>());
+        }
+        builderTrait.getProperties().addAll(List.of(buildProperties));
+        traitsSpec.setBuilder(builderTrait);
+    }
+
+    public static void configureProperties(Traits traitsSpec, String[] 
properties) {
+        if (properties == null || properties.length == 0) {
+            return;
+        }
+
+        Camel camelTrait = 
Optional.ofNullable(traitsSpec.getCamel()).orElseGet(Camel::new);
+        if (camelTrait.getProperties() == null) {
+            camelTrait.setProperties(new ArrayList<>());
+        }
+        camelTrait.getProperties().addAll(List.of(properties));
+        traitsSpec.setCamel(camelTrait);
+    }
+
+    public static void configureOpenApiSpec(Traits traitsSpec, String openApi) 
{
+        if (openApi == null || !openApi.startsWith("configmap:")) {
+            return;
+        }
+
+        Openapi openapiTrait = 
Optional.ofNullable(traitsSpec.getOpenapi()).orElseGet(Openapi::new);
+        if (openapiTrait.getConfigmaps() == null) {
+            openapiTrait.setConfigmaps(new ArrayList<>());
+        }
+        openapiTrait.getConfigmaps().add(openApi);
+        traitsSpec.setOpenapi(openapiTrait);
+    }
+
+    public static void configureMountTrait(Traits traitsSpec, String[] 
configs, String[] resources, String[] volumes) {
+        if (configs == null && resources == null && volumes == null) {
+            return;
+        }
+
+        Mount mountTrait = 
Optional.ofNullable(traitsSpec.getMount()).orElseGet(Mount::new);
+
+        if (configs != null && configs.length > 0) {
+            if (mountTrait.getConfigs() == null) {
+                mountTrait.setConfigs(new ArrayList<>());
+            }
+            mountTrait.getConfigs().addAll(List.of(configs));
+        }
+
+        if (resources != null && resources.length > 0) {
+            if (mountTrait.getResources() == null) {
+                mountTrait.setResources(new ArrayList<>());
+            }
+            mountTrait.getResources().addAll(List.of(resources));
+        }
+
+        if (volumes != null && volumes.length > 0) {
+            if (mountTrait.getVolumes() == null) {
+                mountTrait.setVolumes(new ArrayList<>());
+            }
+            mountTrait.getVolumes().addAll(List.of(volumes));
+        }
+
+        traitsSpec.setMount(mountTrait);
+    }
+
+    public static void configureContainerImage(
+            Traits traitsSpec, String image, String imageRegistry, String 
imageGroup, String imageName, String version) {
+        Container containerTrait = 
Optional.ofNullable(traitsSpec.getContainer()).orElseGet(Container::new);
+        if (image != null) {
+            containerTrait.setImage(image);
+            traitsSpec.setContainer(containerTrait);
+        } else if (containerTrait.getImage() == null) {
+            String registryPrefix = "";
+            if ("minikube".equals(imageRegistry) || 
"minikube-registry".equals(imageRegistry)) {
+                registryPrefix = "localhost:5000/";
+            } else if ("kind".equals(imageRegistry) || 
"kind-registry".equals(imageRegistry)) {
+                registryPrefix = "localhost:5001/";
+            } else if (imageRegistry != null && !imageRegistry.isEmpty()) {
+                registryPrefix = imageRegistry + "/";
+            }
+
+            imageGroup = Optional.ofNullable(imageGroup).orElse("");
+            if (!imageGroup.isEmpty()) {
+                containerTrait.setImage("%s%s/%s:%s".formatted(registryPrefix, 
imageGroup, imageName, version));
+            } else {
+                containerTrait.setImage("%s%s:%s".formatted(registryPrefix, 
imageName, version));
+            }
+
+            // Plain export command always exposes a health endpoint on 8080.
+            // Skip this, when we decide that the health endpoint can be 
disabled.
+            if (containerTrait.getPort() == null) {
+                
containerTrait.setPortName(ContainerTrait.DEFAULT_CONTAINER_PORT_NAME);
+                containerTrait.setPort((long) 
ContainerTrait.DEFAULT_CONTAINER_PORT);
+            }
+
+            traitsSpec.setContainer(containerTrait);
+        }
+    }
+
+}

Reply via email to