This is an automated email from the ASF dual-hosted git repository.

cdeppisch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 4912c140be44a2250e592271174221a704b96215
Author: Christoph Deppisch <cdeppi...@redhat.com>
AuthorDate: Wed Jul 17 10:49:58 2024 +0200

    CAMEL-20985: Camel K integration export
    
    - Add new subcommand to export Camel K integrations/pipes to arbitrary 
project
    - This enables the user to export the integration custom resource as normal 
project that can be run without the Camel K operator being installed on the 
Kubernetes cluster
    - Export automatically configures the Kubernetes deployment manifest with 
the Camel K integration traits
---
 .../dsl/jbang/core/commands/k/CamelKPlugin.java    |   1 +
 .../jbang/core/commands/k/IntegrationExport.java   | 126 +++++++++++++++++++++
 .../dsl/jbang/core/commands/k/IntegrationRun.java  |  22 +---
 .../core/commands/k/IntegrationExportTest.java     | 107 +++++++++++++++++
 .../dsl/jbang/core/commands/k/integration.yaml     |   3 +
 .../camel/dsl/jbang/core/commands/k/pipe.yaml      |   1 +
 .../core/commands/kubernetes/KubernetesExport.java |  67 +++++------
 .../commands/kubernetes/traits/TraitCatalog.java   |  31 +++++
 .../commands/kubernetes/traits/TraitHelper.java    |  32 +++++-
 9 files changed, 333 insertions(+), 57 deletions(-)

diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java
 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java
index 39b5f2a22f0..19822239340 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/CamelKPlugin.java
@@ -31,6 +31,7 @@ public class CamelKPlugin implements Plugin {
                 .addSubcommand("get", new picocli.CommandLine(new 
IntegrationGet(main)))
                 .addSubcommand("run", new picocli.CommandLine(new 
IntegrationRun(main)))
                 .addSubcommand("bind", new picocli.CommandLine(new Bind(main)))
+                .addSubcommand("export", new picocli.CommandLine(new 
IntegrationExport(main)))
                 .addSubcommand("delete", new picocli.CommandLine(new 
IntegrationDelete(main)))
                 .addSubcommand("logs", new picocli.CommandLine(new 
IntegrationLogs(main)));
 
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
new file mode 100644
index 00000000000..e481bf24337
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/main/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExport.java
@@ -0,0 +1,126 @@
+/*
+ * 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.stream.Collectors;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesExport;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper;
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+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",
+                     description = "Export integration as an arbitrary 
Maven/Gradle project with a Kubernetes deployment manifest",
+                     sortOptions = false)
+public class IntegrationExport extends KubernetesExport {
+
+    private Integration integration;
+    private Pipe pipe;
+
+    public IntegrationExport(CamelJBangMain main) {
+        super(main);
+    }
+
+    public IntegrationExport(CamelJBangMain main, RuntimeType runtime, 
String[] files, String exportDir, String imageGroup,
+                             boolean quiet) {
+        super(main, runtime, files, exportDir, quiet);
+        this.imageGroup = imageGroup;
+    }
+
+    @Override
+    public Integer export() throws Exception {
+        if (files.size() != 1) {
+            if (!quiet) {
+                printer().println("Project export failed - requires single 
Integration or Pipe source file as an argument");
+            }
+            return 1;
+        }
+
+        Source source = SourceHelper.resolveSource(files.get(0));
+        if (source.content().contains("kind: Integration")) {
+            integration = 
KubernetesHelper.yaml(this.getClass().getClassLoader()).loadAs(source.content(),
 Integration.class);
+
+            if (integration.getMetadata().getAnnotations() != null) {
+                this.annotations = 
integration.getMetadata().getAnnotations().entrySet().stream()
+                        .map(entry -> "%s=%s".formatted(entry.getKey(), 
entry.getValue()))
+                        .collect(Collectors.toSet()).toArray(String[]::new);
+            }
+
+            if (integration.getMetadata().getLabels() != null) {
+                this.labels = 
integration.getMetadata().getLabels().entrySet().stream()
+                        .map(entry -> "%s=%s".formatted(entry.getKey(), 
entry.getValue()))
+                        .collect(Collectors.toSet()).toArray(String[]::new);
+            }
+        } else if (source.content().contains("kind: Pipe")) {
+            pipe = 
KubernetesHelper.yaml(this.getClass().getClassLoader()).loadAs(source.content(),
 Pipe.class);
+
+            if (pipe.getMetadata().getAnnotations() != null) {
+                this.annotations = 
pipe.getMetadata().getAnnotations().entrySet().stream()
+                        .map(entry -> "%s=%s".formatted(entry.getKey(), 
entry.getValue()))
+                        .collect(Collectors.toSet()).toArray(String[]::new);
+            }
+
+            if (pipe.getMetadata().getLabels() != null) {
+                this.labels = 
pipe.getMetadata().getLabels().entrySet().stream()
+                        .map(entry -> "%s=%s".formatted(entry.getKey(), 
entry.getValue()))
+                        .collect(Collectors.toSet()).toArray(String[]::new);
+            }
+        } else {
+            if (!quiet) {
+                printer().println("Project export failed - not an Integration 
or Pipe source");
+            }
+            return 1;
+        }
+
+        return super.export();
+    }
+
+    @Override
+    protected Traits getTraitSpec() {
+        if (integration != null && integration.getSpec().getTraits() != null) {
+            return integration.getSpec().getTraits();
+        }
+
+        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);
+        }
+
+        return super.getTraitSpec();
+    }
+
+    @Override
+    protected String getProjectName() {
+        if (integration != null) {
+            return integration.getMetadata().getName();
+        }
+
+        if (pipe != null) {
+            return pipe.getMetadata().getName();
+        }
+        return super.getProjectName();
+    }
+}
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 80a7b5d4f15..e17d49025ae 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
@@ -244,12 +244,7 @@ public class IntegrationRun extends KubernetesBaseCommand {
                     .collect(Collectors.toMap(it -> it[0].trim(), it -> 
it[1].trim())));
         }
 
-        Traits traitsSpec;
-        if (traits != null && traits.length > 0) {
-            traitsSpec = TraitHelper.parseTraits(traits);
-        } else {
-            traitsSpec = new Traits();
-        }
+        Traits traitsSpec = TraitHelper.parseTraits(traits);
 
         if (image != null) {
             TraitHelper.configureContainerImage(traitsSpec, image, null, null, 
null, null);
@@ -320,20 +315,7 @@ public class IntegrationRun extends KubernetesBaseCommand {
                     TraitHelper.configureContainerImage(traitsSpec, image, 
"quay.io", null, integration.getMetadata().getName(),
                             "1.0-SNAPSHOT");
 
-                    if (traitProfile != null) {
-                        new 
TraitCatalog().traitsForProfile(TraitProfile.valueOf(traitProfile.toUpperCase(Locale.US)))
-                                .forEach(t -> {
-                                    if (t.configure(traitsSpec, context)) {
-                                        t.apply(traitsSpec, context);
-                                    }
-                                });
-                    } else {
-                        new TraitCatalog().allTraits().forEach(t -> {
-                            if (t.configure(traitsSpec, context)) {
-                                t.apply(traitsSpec, context);
-                            }
-                        });
-                    }
+                    new TraitCatalog().apply(traitsSpec, context, 
traitProfile);
 
                     printer().println(
                             
context.buildItems().stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---")));
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.java
new file mode 100644
index 00000000000..ca6526cd88f
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/java/org/apache/camel/dsl/jbang/core/commands/k/IntegrationExportTest.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.k;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import io.fabric8.kubernetes.api.model.apps.Deployment;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait;
+import org.apache.camel.dsl.jbang.core.common.RuntimeType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class IntegrationExportTest {
+
+    private File workingDir;
+
+    @BeforeEach
+    public void setup() throws IOException {
+        workingDir = 
Files.createTempDirectory("camel-k-integration-export").toFile();
+        workingDir.deleteOnExit();
+    }
+
+    @Test
+    public void shouldExportFromIntegration() throws Exception {
+        IntegrationExport command
+                = createCommand(new String[] { 
"classpath:org/apache/camel/dsl/jbang/core/commands/k/integration.yaml" },
+                        workingDir.toString());
+        int exit = command.doCall();
+
+        Assertions.assertEquals(0, exit);
+
+        Deployment deployment = getDeployment(workingDir);
+        Assertions.assertEquals("routes", deployment.getMetadata().getName());
+        Assertions.assertEquals(1, 
deployment.getSpec().getTemplate().getSpec().getContainers().size());
+        Assertions.assertEquals("routes", 
deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL));
+        Assertions.assertEquals("routes", 
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getName());
+        Assertions.assertEquals(1, 
deployment.getSpec().getSelector().getMatchLabels().size());
+        Assertions.assertEquals("routes",
+                
deployment.getSpec().getSelector().getMatchLabels().get(BaseTrait.INTEGRATION_LABEL));
+        Assertions.assertEquals("quay.io/camel-test/routes:1.0-SNAPSHOT",
+                
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
+        Assertions.assertEquals(1, 
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().size());
+        Assertions.assertEquals("MY_ENV_VAR",
+                
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getName());
+        Assertions.assertEquals("foo",
+                
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getValue());
+    }
+
+    @Test
+    public void shouldExportFromPipe() throws Exception {
+        IntegrationExport command = createCommand(
+                new String[] { 
"classpath:org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml" }, 
workingDir.toString());
+        int exit = command.doCall();
+
+        Assertions.assertEquals(0, exit);
+
+        Deployment deployment = getDeployment(workingDir);
+        Assertions.assertEquals("timer-to-log", 
deployment.getMetadata().getName());
+        Assertions.assertEquals(1, 
deployment.getSpec().getTemplate().getSpec().getContainers().size());
+        Assertions.assertEquals("timer-to-log", 
deployment.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL));
+        Assertions.assertEquals("timer-to-log", 
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getName());
+        Assertions.assertEquals(1, 
deployment.getSpec().getSelector().getMatchLabels().size());
+        Assertions.assertEquals("timer-to-log",
+                
deployment.getSpec().getSelector().getMatchLabels().get(BaseTrait.INTEGRATION_LABEL));
+        Assertions.assertEquals("quay.io/camel-test/timer-to-log:1.0-SNAPSHOT",
+                
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
+        Assertions.assertEquals(1, 
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().size());
+        Assertions.assertEquals("MY_ENV_VAR",
+                
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getName());
+        Assertions.assertEquals("foo",
+                
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getEnv().get(0).getValue());
+    }
+
+    private IntegrationExport createCommand(String[] files, String exportDir) {
+        return new IntegrationExport(
+                new CamelJBangMain(),
+                RuntimeType.quarkus, files, exportDir, "camel-test", false);
+    }
+
+    private Deployment getDeployment(File workingDir) throws IOException {
+        try (FileInputStream fis = new FileInputStream(new File(workingDir, 
"src/main/kubernetes/kubernetes.yml"))) {
+            return KubernetesHelper.yaml().loadAs(fis, Deployment.class);
+        }
+    }
+
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
index a3d6d058745..1578da98df1 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/integration.yaml
@@ -30,5 +30,8 @@ spec:
               constant: Hello Camel !!!
           - to: log:info
   traits:
+    environment:
+      vars:
+        - MY_ENV_VAR=foo
     logging:
       level: DEBUG
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml
 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml
index 5ad3d12a255..b6ed18dcd70 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-k/src/test/resources/org/apache/camel/dsl/jbang/core/commands/k/pipe.yaml
@@ -21,6 +21,7 @@ metadata:
   name: timer-to-log
   annotations:
     camel.apache.org/operator.id: camel-k
+    trait.camel.apache.org/environment.vars: "MY_ENV_VAR=foo"
 spec:
   source:
     ref:
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 7d0335f4d06..fc2a1ca264c 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
@@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -43,62 +42,62 @@ import picocli.CommandLine.Command;
 
 @Command(name = "export", description = "Export as Maven/Gradle project that 
contains a Kubernetes deployment manifest",
          sortOptions = false)
-class KubernetesExport extends Export {
+public class KubernetesExport extends Export {
 
     @CommandLine.Option(names = { "--trait-profile" }, description = "The 
trait profile to use for the deployment.")
-    String traitProfile;
+    protected String traitProfile;
 
     @CommandLine.Option(names = { "--property" },
                         description = "Add a runtime property or properties 
file from a path, a config map or a secret (syntax: 
[my-key=my-value|file:/path/to/my-conf.properties|[configmap|secret]:name]).")
-    String[] properties;
+    protected String[] properties;
 
     @CommandLine.Option(names = { "--config" },
                         description = "Add a runtime configuration from a 
ConfigMap or a Secret (syntax: [configmap|secret]:name[/key], where name 
represents the configmap/secret name and key optionally represents the 
configmap/secret key to be filtered).")
-    String[] configs;
+    protected String[] configs;
 
     @CommandLine.Option(names = { "--resource" },
                         description = "Add a runtime resource from a Configmap 
or a Secret (syntax: [configmap|secret]:name[/key][@path], where name 
represents the configmap/secret name, key optionally represents the 
configmap/secret key to be filtered and path represents the destination path).")
-    String[] resources;
+    protected String[] resources;
 
     @CommandLine.Option(names = { "--open-api-spec" }, description = "Add an 
OpenAPI spec (syntax: [configmap|file]:name).")
-    String[] openApis;
+    protected String[] openApis;
 
     @CommandLine.Option(names = { "--env" },
                         description = "Set an environment variable in the 
integration container, for instance \"-e MY_VAR=my-value\".")
-    String[] envVars;
+    protected String[] envVars;
 
     @CommandLine.Option(names = { "--volume" },
                         description = "Mount a volume into the integration 
container, for instance \"-v pvcname:/container/path\".")
-    String[] volumes;
+    protected String[] volumes;
 
     @CommandLine.Option(names = { "--connect" },
                         description = "A Service that the integration should 
bind to, specified as [[apigroup/]version:]kind:[namespace/]name.")
-    String[] connects;
+    protected String[] connects;
 
     @CommandLine.Option(names = { "--annotation" },
                         description = "Add an annotation to the integration. 
Use name values pairs like \"--annotation my.company=hello\".")
-    String[] annotations;
+    protected String[] annotations;
 
     @CommandLine.Option(names = { "--label" },
                         description = "Add a label to the integration. Use 
name values pairs like \"--label my.company=hello\".")
-    String[] labels;
+    protected String[] labels;
 
     @CommandLine.Option(names = { "--trait" },
                         description = "Add a trait configuration to the 
integration. Use name values pairs like \"--trait trait.name.config=hello\".")
-    String[] traits;
+    protected String[] traits;
 
     @CommandLine.Option(names = { "--image" },
                         description = "The image name to be built.")
-    String image;
+    protected String image;
 
     @CommandLine.Option(names = { "--image-registry" },
                         defaultValue = "quay.io",
                         description = "The image registry to hold the app 
container image.")
-    String imageRegistry = "quay.io";
+    protected String imageRegistry = "quay.io";
 
     @CommandLine.Option(names = { "--image-group" },
                         description = "The image registry group used to push 
images to.")
-    String imageGroup;
+    protected String imageGroup;
 
     public KubernetesExport(CamelJBangMain main) {
         super(main);
@@ -206,12 +205,7 @@ class KubernetesExport extends Export {
             context.setProfile(TraitProfile.valueOf(traitProfile));
         }
 
-        Traits traitsSpec;
-        if (traits != null && traits.length > 0) {
-            traitsSpec = TraitHelper.parseTraits(traits);
-        } else {
-            traitsSpec = new Traits();
-        }
+        Traits traitsSpec = getTraitSpec();
 
         TraitHelper.configureMountTrait(traitsSpec, configs, resources, 
volumes);
         TraitHelper.configureOpenApiSpec(traitsSpec, openApis);
@@ -252,19 +246,7 @@ class KubernetesExport extends Export {
             printer().println("Building Kubernetes manifest ...");
         }
 
-        if (traitProfile != null) {
-            new 
TraitCatalog().traitsForProfile(TraitProfile.valueOf(traitProfile.toUpperCase(Locale.US))).forEach(t
 -> {
-                if (t.configure(traitsSpec, context)) {
-                    t.apply(traitsSpec, context);
-                }
-            });
-        } else {
-            new TraitCatalog().allTraits().forEach(t -> {
-                if (t.configure(traitsSpec, context)) {
-                    t.apply(traitsSpec, context);
-                }
-            });
-        }
+        new TraitCatalog().apply(traitsSpec, context, traitProfile);
 
         String yaml = 
context.buildItems().stream().map(KubernetesHelper::dumpYaml).collect(Collectors.joining("---\n"));
         safeCopy(new 
ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8)),
@@ -277,6 +259,19 @@ class KubernetesExport extends Export {
         return 0;
     }
 
+    protected Traits getTraitSpec() {
+        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[] {}, annotations);
+        } else {
+            traitsSpec = new Traits();
+        }
+
+        return traitsSpec;
+    }
+
     private String resolveImageRegistry() {
         String resolvedImageRegistry = null;
         if (image != null) {
@@ -332,7 +327,7 @@ class KubernetesExport extends Export {
     /**
      * Configurer used to customize internal options for the Export command.
      */
-    record ExportConfigurer(RuntimeType runtime,
+    public record ExportConfigurer(RuntimeType runtime,
             String quarkusVersion,
             boolean symbolicLink,
             boolean mavenWrapper,
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
index cb18dcee671..d566fd22d60 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitCatalog.java
@@ -19,8 +19,16 @@ package 
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.stream.Collectors;
 
+import org.apache.camel.v1.integrationspec.Traits;
+
+/**
+ * Catalog of traits that get applied to a trait context in order to generate 
a set of Kubernetes resources as a
+ * manifest. Traits may be filtered according to a given trait profile. The 
given trait specification holds the trait
+ * configuration for the applied traits.
+ */
 public class TraitCatalog {
 
     private final List<Trait> traits = new ArrayList<>();
@@ -46,4 +54,27 @@ public class TraitCatalog {
     public void register(Trait trait) {
         traits.add(trait);
     }
+
+    /**
+     * Applies traits in this catalog for given profile or all traits if 
profile is not set.
+     *
+     * @param traitsSpec   the trait configuration spec.
+     * @param context      the trait context.
+     * @param traitProfile the optional trait profile to select traits.
+     */
+    public void apply(Traits traitsSpec, TraitContext context, String 
traitProfile) {
+        if (traitProfile != null) {
+            new 
TraitCatalog().traitsForProfile(TraitProfile.valueOf(traitProfile.toUpperCase(Locale.US))).forEach(t
 -> {
+                if (t.configure(traitsSpec, context)) {
+                    t.apply(traitsSpec, context);
+                }
+            });
+        } else {
+            new TraitCatalog().allTraits().forEach(t -> {
+                if (t.configure(traitsSpec, context)) {
+                    t.apply(traitsSpec, context);
+                }
+            });
+        }
+    }
 }
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 14bce2bb89f..9001489977a 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
@@ -24,6 +24,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper;
 import org.apache.camel.util.StringHelper;
@@ -53,9 +55,37 @@ public final class TraitHelper {
      * @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.
+     * @return
+     */
+    public static Traits parseTraits(String[] traits, String[] annotations) {
         Map<String, Map<String, Object>> traitConfigMap = new HashMap<>();
 
-        for (String traitExpression : traits) {
+        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) {
             //traitName.key=value
             final String[] trait = traitExpression.split("\\.", 2);
             final String[] traitConfig = trait[1].split("=", 2);

Reply via email to