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


The following commit(s) were added to refs/heads/main by this push:
     new 77956acc318 CAMEL-20983: Add JBang Knative service trait
77956acc318 is described below

commit 77956acc3188018dcebfe80fbcd585bc55bd1831
Author: Christoph Deppisch <cdeppi...@redhat.com>
AuthorDate: Mon Jul 22 15:48:19 2024 +0200

    CAMEL-20983: Add JBang Knative service trait
    
    - Add Knative service trait to autoconfigure Knative serving service 
resource
    - Add service and Knative service trait options documentation
---
 .../modules/ROOT/pages/camel-jbang-kubernetes.adoc | 105 ++++++++++++++-
 .../camel-jbang-plugin-kubernetes/pom.xml          |  13 +-
 .../core/commands/kubernetes/KubernetesExport.java |   7 +
 .../core/commands/kubernetes/KubernetesRun.java    |   4 +
 .../kubernetes/traits/AnnotationTrait.java         |   4 +
 .../commands/kubernetes/traits/ContainerTrait.java |   2 +-
 .../kubernetes/traits/DeploymentTrait.java         |  16 ++-
 .../commands/kubernetes/traits/ServiceTrait.java   |  31 +----
 .../commands/kubernetes/traits/TraitCatalog.java   |   2 +
 .../commands/kubernetes/traits/TraitContext.java   |  24 +++-
 .../commands/kubernetes/traits/TraitHelper.java    |  31 +++++
 .../KnativeBaseTrait.java}                         |  27 ++--
 .../traits/knative/KnativeServiceTrait.java        | 142 +++++++++++++++++++++
 .../commands/kubernetes/KubernetesExportTest.java  |  52 ++++++++
 parent/pom.xml                                     |   1 +
 15 files changed, 407 insertions(+), 54 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 70f04e90c12..e9a06d01e11 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-kubernetes.adoc
@@ -97,6 +97,9 @@ The Camel JBang Kubernetes export command provides several 
options to customize
 |--trait-profile
 |The trait profile to use for the deployment.
 
+|--service-account
+|The service account used to run the application.
+
 |--dependency
 |Adds dependency that should be included, use "camel:" prefix for a Camel 
component, "mvn:org.my:app:1.0" for a Maven dependency.
 
@@ -143,7 +146,7 @@ The Camel JBang Kubernetes export command provides several 
options to customize
 |The image registry group used to push images to.
 |=======================================================================
 
-=== Kubernetes manifest options
+== Kubernetes manifest options
 
 The Kubernetes manifest (kubernetes.yml) describes all resources to 
successfully run the application on Kubernetes.
 The manifest usually holds the deployment, a service definition, config maps 
and much more.
@@ -371,6 +374,106 @@ spec:
 ----
 <1> Environment variables set in the container specification
 
+=== Service trait options
+
+The Service trait enhances the Kubernetes manifest with a Service resource so 
that the application can be accessed by other components in the same namespace.
+The service resource exposes the application with a protocol (e.g. TCP/IP) on 
a given port and uses either `ClusterIP`, `NodePort` or `LoadBalancer` type.
+
+The Camel JBang plugin automatically inspects the Camel routes for exposed 
Http services and adds the service resource when applicable.
+This means when one of the Camel routes exposes a Http service (for instance 
by using the `platform-http` component) the Kubernetes manifest also creates a 
Kubernetes Service resource besides the arbitrary Deployment.
+
+You can customize the generated Kubernetes service resource with trait options:
+
+[cols="2m,1m,5a"]
+|===
+|Property | Type | Description
+
+| service.type
+| string
+| The type of service to be used, either 'ClusterIP', 'NodePort' or 
'LoadBalancer'.
+
+| container.service-port
+| int
+| To configure under which service port the container port is to be exposed 
(default `80`).
+
+| container.service-port-name
+| string
+| To configure under which service port name the container port is to be 
exposed (default `http`).
+
+|===
+
+=== Knative service trait options
+
+https://knative.dev/docs/serving/[Knative serving] defines a set of resources 
on Kubernetes to handle Serverless workloads with automatic scaling and 
scale-to-zero functionality.
+
+When Knative serving is available on the target Kubernetes cluster you may 
want to use the Knative service resource instead of an arbitrary Kubernetes 
service resource.
+The Knative service trait will create such a resource as part of the 
Kubernetes manifest.
+
+NOTE: You need to enable the Knative service trait with `--trait 
knative-service.enabled=true` option. Otherwise the Camel JBang export will 
always create an arbitrary Kubernetes service resource.
+
+The trait offers following options for customization:
+
+[cols="2m,1m,5a"]
+|===
+|Property | Type | Description
+
+| knative-service.enabled
+| bool
+| Can be used to enable or disable a trait. All traits share this common 
property.
+
+| knative-service.annotations
+| map[string]string
+| The annotations added to route.
+This can be used to set knative service specific annotations
+CLI usage example: -t 
"knative-service.annotations.'haproxy.router.openshift.io/balance'=true"
+
+| knative-service.class
+| string
+| Configures the Knative autoscaling class property (e.g. to set 
`hpa.autoscaling.knative.dev` or `kpa.autoscaling.knative.dev` autoscaling).
+
+Refer to the Knative documentation for more information.
+
+| knative-service.autoscaling-metric
+| string
+| Configures the Knative autoscaling metric property (e.g. to set 
`concurrency` based or `cpu` based autoscaling).
+
+Refer to the Knative documentation for more information.
+
+| knative-service.autoscaling-target
+| int
+| Sets the allowed concurrency level or CPU percentage (depending on the 
autoscaling metric) for each Pod.
+
+Refer to the Knative documentation for more information.
+
+| knative-service.min-scale
+| int
+| The minimum number of Pods that should be running at any time for the 
integration. It's **zero** by default, meaning that
+the integration is scaled down to zero when not used for a configured amount 
of time.
+
+Refer to the Knative documentation for more information.
+
+| knative-service.max-scale
+| int
+| An upper bound for the number of Pods that can be running in parallel for 
the integration.
+Knative has its own cap value that depends on the installation.
+
+Refer to the Knative documentation for more information.
+
+| knative-service.rollout-duration
+| string
+| Enables to gradually shift traffic to the latest Revision and sets the 
rollout duration.
+It's disabled by default and must be expressed as a Golang `time.Duration` 
string representation,
+rounded to a second precision.
+
+| knative-service.visibility
+| string
+| Setting `cluster-local`, Knative service becomes a private service.
+Specifically, this option applies the `networking.knative.dev/visibility` 
label to Knative service.
+
+Refer to the Knative documentation for more information.
+
+|===
+
 === Mount trait options
 
 The mount trait is able to configure volume mounts on the Deployment resource 
in order to inject data from Kubernetes resources such as config maps or 
secrets.
diff --git a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/pom.xml 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/pom.xml
index d5f8da622ad..5a7f7024b1c 100644
--- a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/pom.xml
@@ -47,14 +47,21 @@
           <artifactId>camel-jbang-core</artifactId>
         </dependency>
 
-        <!-- kubernetes -->
+        <!-- Kubernetes -->
         <dependency>
             <groupId>io.fabric8</groupId>
             <artifactId>kubernetes-client</artifactId>
             <version>${kubernetes-client-version}</version>
         </dependency>
 
-        <!-- camel k-->
+        <!-- Knative model -->
+        <dependency>
+            <groupId>io.fabric8</groupId>
+            <artifactId>knative-model</artifactId>
+            <version>${knative-client-version}</version>
+        </dependency>
+
+        <!-- Camel K-->
         <!-- TODO: remove dependency in favor of using trait domain model in 
Camel JBang -->
         <dependency>
             <groupId>org.apache.camel.k</groupId>
@@ -62,7 +69,7 @@
             <version>${camel-k-version}</version>
         </dependency>
 
-        <!-- test dependencies -->
+        <!-- Test dependencies -->
         <dependency>
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-test-junit5</artifactId>
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 7347568a616..23bb16ab879 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
@@ -52,6 +52,9 @@ public class KubernetesExport extends Export {
     @CommandLine.Option(names = { "--trait-profile" }, description = "The 
trait profile to use for the deployment.")
     protected String traitProfile;
 
+    @CommandLine.Option(names = { "--service-account" }, description = "The 
service account used to run the application.")
+    protected String serviceAccount;
+
     @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]).")
     protected String[] properties;
@@ -221,6 +224,10 @@ public class KubernetesExport extends Export {
             context.setProfile(TraitProfile.valueOf(traitProfile));
         }
 
+        if (serviceAccount != null) {
+            context.setServiceAccount(serviceAccount);
+        }
+
         Traits traitsSpec = getTraitSpec();
 
         TraitHelper.configureMountTrait(traitsSpec, configs, resources, 
volumes);
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
index 959bb7c2f07..eb10202c9e8 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesRun.java
@@ -52,6 +52,9 @@ public class KubernetesRun extends KubernetesBaseCommand {
     @CommandLine.Option(names = { "--trait-profile" }, description = "The 
trait profile to use for the deployment.")
     String traitProfile;
 
+    @CommandLine.Option(names = { "--service-account" }, description = "The 
service account used to run the application.")
+    String serviceAccount;
+
     @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;
@@ -182,6 +185,7 @@ public class KubernetesRun extends KubernetesBaseCommand {
         export.imageRegistry = imageRegistry;
         export.imageGroup = imageGroup;
         export.traitProfile = traitProfile;
+        export.serviceAccount = serviceAccount;
         export.properties = properties;
         export.configs = configs;
         export.resources = resources;
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/AnnotationTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/AnnotationTrait.java
index fcb1d6a5370..e48ba14c2c3 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/AnnotationTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/AnnotationTrait.java
@@ -40,5 +40,9 @@ public class AnnotationTrait extends BaseTrait {
                 s -> s.editOrNewMetadata()
                         .addToAnnotations(context.getAnnotations())
                         .endMetadata());
+        context.doWithKnativeServices(
+                s -> s.editOrNewMetadata()
+                        .addToAnnotations(context.getAnnotations())
+                        .endMetadata());
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java
index 83531df2e75..b7a8e24a929 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ContainerTrait.java
@@ -53,7 +53,7 @@ public class ContainerTrait extends BaseTrait {
             
container.withImagePullPolicy(containerTrait.getImagePullPolicy().getValue());
         }
 
-        if (containerTrait.getPort() != null || 
context.getService().isPresent()) {
+        if (containerTrait.getPort() != null || 
context.getService().isPresent() || context.getKnativeService().isPresent()) {
             container.addToPorts(new ContainerPortBuilder()
                     
.withName(Optional.ofNullable(containerTrait.getPortName()).orElse(DEFAULT_CONTAINER_PORT_NAME))
                     
.withContainerPort(Optional.ofNullable(containerTrait.getPort()).map(Long::intValue).orElse(8080))
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java
index e76b1dafe2c..46241b65a61 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/DeploymentTrait.java
@@ -36,7 +36,7 @@ public class DeploymentTrait extends BaseTrait {
 
     @Override
     public void apply(Traits traitConfig, TraitContext context) {
-        context.add(new DeploymentBuilder()
+        DeploymentBuilder deployment = new DeploymentBuilder()
                 .withNewMetadata()
                 .withName(context.getName())
                 .endMetadata()
@@ -45,6 +45,18 @@ public class DeploymentTrait extends BaseTrait {
                         .withMatchLabels(
                                 Collections.singletonMap(INTEGRATION_LABEL, 
context.getName()))
                         .build())
-                .endSpec());
+                .endSpec();
+
+        if (context.getServiceAccount() != null) {
+            deployment.editSpec()
+                    .editOrNewTemplate()
+                    .editOrNewSpec()
+                    .withServiceAccountName(context.getServiceAccount())
+                    .endSpec()
+                    .endTemplate()
+                    .endSpec();
+        }
+
+        context.add(deployment);
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java
index fc19d1226b2..555a5708e9a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/ServiceTrait.java
@@ -23,23 +23,21 @@ import java.util.Optional;
 import io.fabric8.kubernetes.api.model.IntOrString;
 import io.fabric8.kubernetes.api.model.ServiceBuilder;
 import io.fabric8.kubernetes.api.model.ServicePortBuilder;
-import org.apache.camel.catalog.CamelCatalog;
-import org.apache.camel.dsl.jbang.core.commands.kubernetes.MetadataHelper;
-import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.support.SourceMetadata;
-import org.apache.camel.dsl.jbang.core.common.Source;
 import org.apache.camel.v1.integrationspec.Traits;
 import org.apache.camel.v1.integrationspec.traits.Container;
 import org.apache.camel.v1.integrationspec.traits.Service;
 
 public class ServiceTrait extends BaseTrait {
 
+    public static final int SERVICE_TRAIT_ORDER = 1500;
+
     public ServiceTrait() {
-        super("service", 1500);
+        super("service", SERVICE_TRAIT_ORDER);
     }
 
     @Override
     public boolean configure(Traits traitConfig, TraitContext context) {
-        if (context.getService().isPresent()) {
+        if (context.getService().isPresent() || 
context.getKnativeService().isPresent()) {
             return false;
         }
 
@@ -48,31 +46,14 @@ public class ServiceTrait extends BaseTrait {
             return traitConfig.getService().getEnabled();
         }
 
-        try {
-            boolean exposesHttpServices = false;
-            CamelCatalog catalog = context.getCatalog();
-            if (context.getSources() != null) {
-                for (Source source : context.getSources()) {
-                    SourceMetadata metadata = 
MetadataHelper.readFromSource(catalog, source);
-                    if (MetadataHelper.exposesHttpServices(catalog, metadata)) 
{
-                        exposesHttpServices = true;
-                        break;
-                    }
-                }
-            }
-
-            return exposesHttpServices;
-        } catch (Exception e) {
-            context.printer().printf("Failed to apply service trait %s%n", 
e.getMessage());
-            return false;
-        }
+        return TraitHelper.exposesHttpService(context);
     }
 
     @Override
     public void apply(Traits traitConfig, TraitContext context) {
         Service serviceTrait = 
Optional.ofNullable(traitConfig.getService()).orElseGet(Service::new);
         String serviceType = 
Optional.ofNullable(serviceTrait.getType()).map(Service.Type::getValue)
-                .orElse(Service.Type.NODEPORT.getValue());
+                .orElse(Service.Type.CLUSTERIP.getValue());
 
         Container containerTrait = 
Optional.ofNullable(traitConfig.getContainer()).orElseGet(Container::new);
 
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 6edbf1781aa..017211d904b 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
@@ -22,6 +22,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.stream.Collectors;
 
+import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.knative.KnativeServiceTrait;
 import org.apache.camel.v1.integrationspec.Traits;
 
 /**
@@ -35,6 +36,7 @@ public class TraitCatalog {
 
     public TraitCatalog() {
         register(new DeploymentTrait());
+        register(new KnativeServiceTrait());
         register(new ServiceTrait());
         register(new ContainerTrait());
         register(new EnvTrait());
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
index 298caff1504..a4682171fcb 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/TraitContext.java
@@ -50,6 +50,8 @@ public class TraitContext {
     private final String name;
     private final String version;
 
+    private String serviceAccount;
+
     private CamelCatalog catalog;
 
     private final Printer printer;
@@ -93,13 +95,14 @@ public class TraitContext {
         this.profile = profile;
     }
 
-    /**
-     * @param visitor
-     */
     public void doWithServices(Visitor<ServiceBuilder> visitor) {
         resourceRegistry.forEach(r -> r.accept(ServiceBuilder.class, visitor));
     }
 
+    public void 
doWithKnativeServices(Visitor<io.fabric8.knative.serving.v1.ServiceBuilder> 
visitor) {
+        resourceRegistry.forEach(r -> 
r.accept(io.fabric8.knative.serving.v1.ServiceBuilder.class, visitor));
+    }
+
     public void doWithDeployments(Visitor<DeploymentBuilder> visitor) {
         resourceRegistry.forEach(r -> r.accept(DeploymentBuilder.class, 
visitor));
     }
@@ -122,6 +125,13 @@ public class TraitContext {
                 .findFirst();
     }
 
+    public Optional<io.fabric8.knative.serving.v1.ServiceBuilder> 
getKnativeService() {
+        return resourceRegistry.stream()
+                .filter(it -> 
it.getClass().isAssignableFrom(io.fabric8.knative.serving.v1.ServiceBuilder.class))
+                .map(it -> (io.fabric8.knative.serving.v1.ServiceBuilder) it)
+                .findFirst();
+    }
+
     public String getName() {
         return name;
     }
@@ -170,4 +180,12 @@ public class TraitContext {
     public Source[] getSources() {
         return sources.toArray(Source[]::new);
     }
+
+    public void setServiceAccount(String serviceAccount) {
+        this.serviceAccount = serviceAccount;
+    }
+
+    public String getServiceAccount() {
+        return serviceAccount;
+    }
 }
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 9001489977a..0ada9f06b2c 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
@@ -27,7 +27,11 @@ import java.util.Set;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
+import org.apache.camel.catalog.CamelCatalog;
 import org.apache.camel.dsl.jbang.core.commands.kubernetes.KubernetesHelper;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.MetadataHelper;
+import 
org.apache.camel.dsl.jbang.core.commands.kubernetes.support.SourceMetadata;
+import org.apache.camel.dsl.jbang.core.common.Source;
 import org.apache.camel.util.StringHelper;
 import org.apache.camel.v1.integrationspec.Traits;
 import org.apache.camel.v1.integrationspec.traits.AddonsBuilder;
@@ -307,4 +311,31 @@ public final class TraitHelper {
             traitsSpec.setContainer(containerTrait);
         }
     }
+
+    /**
+     * Inspect sources in context to retrieve routes that expose a Http 
service as an endpoint.
+     *
+     * @param  context the trait context holding all route sources.
+     * @return         true when routes expose a Http service, false otherwise.
+     */
+    public static boolean exposesHttpService(TraitContext context) {
+        try {
+            boolean exposesHttpServices = false;
+            CamelCatalog catalog = context.getCatalog();
+            if (context.getSources() != null) {
+                for (Source source : context.getSources()) {
+                    SourceMetadata metadata = 
MetadataHelper.readFromSource(catalog, source);
+                    if (MetadataHelper.exposesHttpServices(catalog, metadata)) 
{
+                        exposesHttpServices = true;
+                        break;
+                    }
+                }
+            }
+
+            return exposesHttpServices;
+        } catch (Exception e) {
+            context.printer().printf("Failed to apply service trait %s%n", 
e.getMessage());
+            return false;
+        }
+    }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/AnnotationTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeBaseTrait.java
similarity index 55%
copy from 
dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/AnnotationTrait.java
copy to 
dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeBaseTrait.java
index fcb1d6a5370..480b1d4976a 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/AnnotationTrait.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeBaseTrait.java
@@ -15,30 +15,19 @@
  * limitations under the License.
  */
 
-package org.apache.camel.dsl.jbang.core.commands.kubernetes.traits;
+package org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.knative;
 
-import org.apache.camel.v1.integrationspec.Traits;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.TraitProfile;
 
-public class AnnotationTrait extends BaseTrait {
+abstract class KnativeBaseTrait extends BaseTrait {
 
-    public AnnotationTrait() {
-        super("annotations", 11000);
+    public KnativeBaseTrait(String id, int order) {
+        super(id, order);
     }
 
     @Override
-    public boolean configure(Traits traitConfig, TraitContext context) {
-        return true;
-    }
-
-    @Override
-    public void apply(Traits traitConfig, TraitContext context) {
-        context.doWithDeployments(
-                d -> d.editOrNewMetadata()
-                        .addToAnnotations(context.getAnnotations())
-                        .endMetadata());
-        context.doWithServices(
-                s -> s.editOrNewMetadata()
-                        .addToAnnotations(context.getAnnotations())
-                        .endMetadata());
+    public boolean accept(TraitProfile profile) {
+        return TraitProfile.KNATIVE == profile;
     }
 }
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeServiceTrait.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeServiceTrait.java
new file mode 100644
index 00000000000..30c65deee05
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/main/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/traits/knative/KnativeServiceTrait.java
@@ -0,0 +1,142 @@
+/*
+ * 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.knative;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import io.fabric8.knative.serving.v1.ServiceBuilder;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.BaseTrait;
+import org.apache.camel.dsl.jbang.core.commands.kubernetes.traits.ServiceTrait;
+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.util.ObjectHelper;
+import org.apache.camel.v1.integrationspec.Traits;
+import org.apache.camel.v1.integrationspec.traits.KnativeService;
+
+public class KnativeServiceTrait extends KnativeBaseTrait {
+
+    // Auto-scaling annotations
+    private static final String knativeServingClassAnnotation = 
"autoscaling.knative.dev/class";
+    private static final String knativeServingMetricAnnotation = 
"autoscaling.knative.dev/metric";
+    private static final String knativeServingTargetAnnotation = 
"autoscaling.knative.dev/target";
+    private static final String knativeServingMinScaleAnnotation = 
"autoscaling.knative.dev/minScale";
+    private static final String knativeServingMaxScaleAnnotation = 
"autoscaling.knative.dev/maxScale";
+    // Rollout annotation
+    private static final String knativeServingRolloutDurationAnnotation = 
"serving.knative.dev/rolloutDuration";
+    // Visibility label
+    private static final String knativeServingVisibilityLabel = 
"networking.knative.dev/visibility";
+
+    public KnativeServiceTrait() {
+        super("knative-service", ServiceTrait.SERVICE_TRAIT_ORDER - 100);
+    }
+
+    @Override
+    public boolean configure(Traits traitConfig, TraitContext context) {
+        if (context.getKnativeService().isPresent()) {
+            return false;
+        }
+
+        if (traitConfig.getKnativeService() != null && 
traitConfig.getKnativeService().getEnabled() != null) {
+            // Knative service either explicitly enabled or disabled
+            return traitConfig.getKnativeService().getEnabled() && 
TraitHelper.exposesHttpService(context);
+        }
+
+        if (traitConfig.getKnative() != null && 
traitConfig.getKnative().getEnabled() != null) {
+            // Knative either explicitly enabled or disabled
+            return traitConfig.getKnative().getEnabled() && 
TraitHelper.exposesHttpService(context);
+        }
+
+        return false;
+    }
+
+    @Override
+    public void apply(Traits traitConfig, TraitContext context) {
+        KnativeService serviceTrait = 
Optional.ofNullable(traitConfig.getKnativeService()).orElseGet(KnativeService::new);
+
+        Map<String, String> serviceAnnotations = new HashMap<>();
+        // Set Knative rollout
+        if (ObjectHelper.isNotEmpty(serviceTrait.getRolloutDuration())) {
+            serviceAnnotations.put(knativeServingRolloutDurationAnnotation, 
serviceTrait.getRolloutDuration());
+        }
+
+        if (serviceTrait.getAnnotations() != null) {
+            serviceAnnotations.putAll(serviceTrait.getAnnotations());
+        }
+
+        Map<String, String> revisionAnnotations = new HashMap<>();
+        // Set Knative auto-scaling
+        if (serviceTrait.get_class() != null && 
ObjectHelper.isNotEmpty(serviceTrait.get_class().getValue())) {
+            revisionAnnotations.put(knativeServingClassAnnotation, 
serviceTrait.get_class().getValue());
+        }
+        if (ObjectHelper.isNotEmpty(serviceTrait.getAutoscalingMetric())) {
+            revisionAnnotations.put(knativeServingMetricAnnotation, 
serviceTrait.getAutoscalingMetric());
+        }
+        if (serviceTrait.getAutoscalingTarget() != null) {
+            revisionAnnotations.put(knativeServingTargetAnnotation, 
serviceTrait.getAutoscalingTarget().toString());
+        }
+        if (serviceTrait.getMinScale() != null && serviceTrait.getMinScale() > 
0) {
+            revisionAnnotations.put(knativeServingMinScaleAnnotation, 
serviceTrait.getMinScale().toString());
+        }
+        if (serviceTrait.getMaxScale() != null && serviceTrait.getMaxScale() > 
0) {
+            revisionAnnotations.put(knativeServingMaxScaleAnnotation, 
serviceTrait.getMaxScale().toString());
+        }
+
+        Map<String, String> serviceLabels = new HashMap<>();
+        serviceLabels.put(BaseTrait.INTEGRATION_LABEL, context.getName());
+
+        // Make sure the Eventing webhook will select the source resource, in 
order to inject the sink information.
+        // This is necessary for Knative environments, that are configured 
with SINK_BINDING_SELECTION_MODE=inclusion.
+        // - 
https://knative.dev/v1.3-docs/eventing/custom-event-source/sinkbinding/create-a-sinkbinding/#optional-choose-sinkbinding-namespace-selection-behavior
+        // - 
https://github.com/knative/operator/blob/release-1.2/docs/configuration.md#specsinkbindingselectionmode
+        serviceLabels.put("bindings.knative.dev/include", "true");
+
+        if (serviceTrait.getVisibility() != null && 
ObjectHelper.isNotEmpty(serviceTrait.getVisibility().getValue())) {
+            serviceLabels.put(knativeServingVisibilityLabel, 
serviceTrait.getVisibility().getValue());
+        }
+
+        ServiceBuilder service = new ServiceBuilder()
+                .withNewMetadata()
+                .withName(context.getName())
+                .addToLabels(serviceLabels)
+                .addToAnnotations(serviceAnnotations)
+                .endMetadata()
+                .withNewSpec()
+                .withNewTemplate()
+                .withNewMetadata()
+                .addToLabels(BaseTrait.INTEGRATION_LABEL, context.getName())
+                .addToAnnotations(revisionAnnotations)
+                .endMetadata()
+                .endTemplate()
+                .endSpec();
+
+        if (context.getServiceAccount() != null) {
+            service.editSpec()
+                    .editTemplate()
+                    .editSpec()
+                    .withServiceAccountName(context.getServiceAccount())
+                    .endSpec()
+                    .endTemplate()
+                    .endSpec();
+        }
+
+        context.add(service);
+    }
+
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
index 069e68ba24b..bc04a4ed4af 100644
--- 
a/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-plugin-kubernetes/src/test/java/org/apache/camel/dsl/jbang/core/commands/kubernetes/KubernetesExportTest.java
@@ -103,6 +103,7 @@ class KubernetesExportTest extends KubernetesBaseTest {
                 
deployment.getSpec().getTemplate().getSpec().getContainers().get(0).getImage());
 
         Assertions.assertFalse(hasService(workingDir));
+        Assertions.assertFalse(hasKnativeService(workingDir));
     }
 
     @ParameterizedTest
@@ -113,6 +114,7 @@ class KubernetesExportTest extends KubernetesBaseTest {
         command.doCall();
 
         Assertions.assertTrue(hasService(workingDir));
+        Assertions.assertFalse(hasKnativeService(workingDir));
 
         Deployment deployment = getDeployment(workingDir);
         Assertions.assertEquals("route-service", 
deployment.getMetadata().getName());
@@ -185,6 +187,52 @@ class KubernetesExportTest extends KubernetesBaseTest {
         Assertions.assertEquals("custom", 
service.getSpec().getPorts().get(0).getTargetPort().getStrVal());
     }
 
+    @ParameterizedTest
+    @MethodSource("runtimeProvider")
+    public void shouldAddKnativeServiceSpec(RuntimeType rt) throws Exception {
+        KubernetesExport command = createCommand(new String[] { 
"classpath:route-service.yaml" },
+                "--image-group=camel-test", "--runtime=" + rt.runtime());
+
+        command.traits = new String[] {
+                "knative-service.enabled=true",
+                "knative-service.class=hpa.autoscaling.knative.dev",
+                "knative-service.autoscaling-metric=cpu",
+                "knative-service.autoscaling-target=80",
+                "knative-service.min-scale=1",
+                "knative-service.max-scale=10",
+                "knative-service.rollout-duration=60",
+                "knative-service.visibility=cluster-local" };
+        command.doCall();
+
+        Assertions.assertFalse(hasService(workingDir));
+        Assertions.assertTrue(hasKnativeService(workingDir));
+
+        io.fabric8.knative.serving.v1.Service service = 
getResource(workingDir, io.fabric8.knative.serving.v1.Service.class)
+                .orElseThrow(() -> new RuntimeCamelException("Missing Knative 
service in Kubernetes manifest"));
+
+        Assertions.assertEquals("route-service", 
service.getMetadata().getName());
+        Assertions.assertEquals(3, service.getMetadata().getLabels().size());
+        Assertions.assertEquals("route-service", 
service.getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL));
+        Assertions.assertEquals("true", 
service.getMetadata().getLabels().get("bindings.knative.dev/include"));
+        Assertions.assertEquals("cluster-local", 
service.getMetadata().getLabels().get("networking.knative.dev/visibility"));
+        Assertions.assertEquals(1, 
service.getMetadata().getAnnotations().size());
+        Assertions.assertEquals("60", 
service.getMetadata().getAnnotations().get("serving.knative.dev/rolloutDuration"));
+        Assertions.assertEquals(1, 
service.getSpec().getTemplate().getMetadata().getLabels().size());
+        Assertions.assertEquals("route-service",
+                
service.getSpec().getTemplate().getMetadata().getLabels().get(BaseTrait.INTEGRATION_LABEL));
+        Assertions.assertEquals(5, 
service.getSpec().getTemplate().getMetadata().getAnnotations().size());
+        Assertions.assertEquals("cpu",
+                
service.getSpec().getTemplate().getMetadata().getAnnotations().get("autoscaling.knative.dev/metric"));
+        Assertions.assertEquals("hpa.autoscaling.knative.dev",
+                
service.getSpec().getTemplate().getMetadata().getAnnotations().get("autoscaling.knative.dev/class"));
+        Assertions.assertEquals("80",
+                
service.getSpec().getTemplate().getMetadata().getAnnotations().get("autoscaling.knative.dev/target"));
+        Assertions.assertEquals("1",
+                
service.getSpec().getTemplate().getMetadata().getAnnotations().get("autoscaling.knative.dev/minScale"));
+        Assertions.assertEquals("10",
+                
service.getSpec().getTemplate().getMetadata().getAnnotations().get("autoscaling.knative.dev/maxScale"));
+    }
+
     @ParameterizedTest
     @MethodSource("runtimeProvider")
     public void shouldAddVolumes(RuntimeType rt) throws Exception {
@@ -365,6 +413,10 @@ class KubernetesExportTest extends KubernetesBaseTest {
         return getResource(workingDir, Service.class).isPresent();
     }
 
+    private boolean hasKnativeService(File workingDir) throws IOException {
+        return getResource(workingDir, 
io.fabric8.knative.serving.v1.Service.class).isPresent();
+    }
+
     private <T extends HasMetadata> Optional<T> getResource(File workingDir, 
Class<T> type) throws IOException {
         try (FileInputStream fis = new FileInputStream(new File(workingDir, 
"src/main/kubernetes/kubernetes.yml"))) {
             List<HasMetadata> resources = kubernetesClient.load(fis).items();
diff --git a/parent/pom.xml b/parent/pom.xml
index 2a4bfbbb305..4185607b86d 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -312,6 +312,7 @@
         <jython-standalone-version>2.7.3</jython-standalone-version>
         <jzlib-version>1.1.3</jzlib-version>
         <kafka-version>3.7.1</kafka-version>
+        <knative-client-version>6.13.1</knative-client-version>
         <kotlin-version>1.9.24</kotlin-version>
         <kotlinpoet-version>1.18.1</kotlinpoet-version>
         <kubernetes-client-version>6.13.1</kubernetes-client-version>


Reply via email to