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

lburgazzoli 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 9701170  CAMEL-16570: make it possible to discover kamelets at runtime
9701170 is described below

commit 97011703af164659ca0fcd19e3e0c9b2717eabf6
Author: Luca Burgazzoli <[email protected]>
AuthorDate: Fri May 7 18:15:13 2021 +0200

    CAMEL-16570: make it possible to discover kamelets at runtime
---
 .../camel/catalog/docs/kamelet-component.adoc      |   3 +-
 .../kamelet/KameletComponentConfigurer.java        |   3 +
 .../apache/camel/component/kamelet/kamelet.json    |   1 +
 .../src/main/docs/kamelet-component.adoc           |   9 +-
 .../apache/camel/component/kamelet/Kamelet.java    |   1 +
 .../camel/component/kamelet/KameletComponent.java  |  45 ++++-
 .../component/kamelet/KameletDiscoveryTest.java    | 100 ++++++++++
 .../camel/impl/engine/DefaultRoutesLoader.java     |   2 +-
 .../dsl/KameletComponentBuilderFactory.java        |  16 ++
 .../modules/ROOT/pages/kamelet-component.adoc      |   3 +-
 .../yaml/common/YamlDeserializationContext.java    |  17 ++
 .../dsl/yaml/common/YamlDeserializerSupport.java   |  54 ++++++
 .../org/apache/camel/routes-loader/kamelet.yaml    |   2 +
 .../camel/dsl/yaml/KameletRoutesBuilderLoader.java |  73 ++++++++
 .../camel/dsl/yaml/YamlRoutesBuilderLoader.java    | 137 ++++----------
 .../dsl/yaml/YamlRoutesBuilderLoaderSupport.java   | 126 +++++++++++++
 .../apache/camel/dsl/yaml/KameletLoaderTest.groovy | 208 +++++++++++++++++++++
 .../camel/dsl/yaml/support/YamlTestSupport.groovy  |  11 +-
 .../test/resources/kamelets/mySetBody.kamelet.yaml |  32 ++++
 19 files changed, 730 insertions(+), 113 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-component.adoc
index 40ad18a..e9168ca 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/kamelet-component.adoc
@@ -27,13 +27,14 @@ kamelet:templateId/routeId[?options]
 
 
 // component options: START
-The Kamelet component supports 7 options, which are listed below.
+The Kamelet component supports 8 options, which are listed below.
 
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
+| *location* (common) | The location of the Kamelets on the file system. | 
classpath:/kamelets | String
 | *routeProperties* (common) | Set route local parameters. |  | Map
 | *templateProperties* (common) | Set template local parameters. |  | Map
 | *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions occurred while the 
consumer is trying to pickup incoming messages, or the likes, will now be 
processed as a message and handled by the routing Error Handler. By default the 
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions, that will be logged at WARN or ERROR level and ignored. | false | 
boolean
diff --git 
a/components/camel-kamelet/src/generated/java/org/apache/camel/component/kamelet/KameletComponentConfigurer.java
 
b/components/camel-kamelet/src/generated/java/org/apache/camel/component/kamelet/KameletComponentConfigurer.java
index 238976e..1cc8ff6 100644
--- 
a/components/camel-kamelet/src/generated/java/org/apache/camel/component/kamelet/KameletComponentConfigurer.java
+++ 
b/components/camel-kamelet/src/generated/java/org/apache/camel/component/kamelet/KameletComponentConfigurer.java
@@ -28,6 +28,7 @@ public class KameletComponentConfigurer extends 
PropertyConfigurerSupport implem
         case "bridgeErrorHandler": 
target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); 
return true;
         case "lazystartproducer":
         case "lazyStartProducer": 
target.setLazyStartProducer(property(camelContext, boolean.class, value)); 
return true;
+        case "location": target.setLocation(property(camelContext, 
java.lang.String.class, value)); return true;
         case "routeproperties":
         case "routeProperties": 
target.setRouteProperties(property(camelContext, java.util.Map.class, value)); 
return true;
         case "templateproperties":
@@ -47,6 +48,7 @@ public class KameletComponentConfigurer extends 
PropertyConfigurerSupport implem
         case "bridgeErrorHandler": return boolean.class;
         case "lazystartproducer":
         case "lazyStartProducer": return boolean.class;
+        case "location": return java.lang.String.class;
         case "routeproperties":
         case "routeProperties": return java.util.Map.class;
         case "templateproperties":
@@ -67,6 +69,7 @@ public class KameletComponentConfigurer extends 
PropertyConfigurerSupport implem
         case "bridgeErrorHandler": return target.isBridgeErrorHandler();
         case "lazystartproducer":
         case "lazyStartProducer": return target.isLazyStartProducer();
+        case "location": return target.getLocation();
         case "routeproperties":
         case "routeProperties": return target.getRouteProperties();
         case "templateproperties":
diff --git 
a/components/camel-kamelet/src/generated/resources/org/apache/camel/component/kamelet/kamelet.json
 
b/components/camel-kamelet/src/generated/resources/org/apache/camel/component/kamelet/kamelet.json
index 36dd720..819ae57 100644
--- 
a/components/camel-kamelet/src/generated/resources/org/apache/camel/component/kamelet/kamelet.json
+++ 
b/components/camel-kamelet/src/generated/resources/org/apache/camel/component/kamelet/kamelet.json
@@ -22,6 +22,7 @@
     "lenientProperties": true
   },
   "componentProperties": {
+    "location": { "kind": "property", "displayName": "Location", "group": 
"common", "label": "", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": "classpath:\/kamelets", "description": "The location of the 
Kamelets on the file system." },
     "routeProperties": { "kind": "property", "displayName": "Route 
Properties", "group": "common", "label": "", "required": false, "type": 
"object", "javaType": "java.util.Map<java.lang.String, java.util.Properties>", 
"deprecated": false, "autowired": false, "secret": false, "description": "Set 
route local parameters." },
     "templateProperties": { "kind": "property", "displayName": "Template 
Properties", "group": "common", "label": "", "required": false, "type": 
"object", "javaType": "java.util.Map<java.lang.String, java.util.Properties>", 
"deprecated": false, "autowired": false, "secret": false, "description": "Set 
template local parameters." },
     "bridgeErrorHandler": { "kind": "property", "displayName": "Bridge Error 
Handler", "group": "consumer", "label": "consumer", "required": false, "type": 
"boolean", "javaType": "boolean", "deprecated": false, "autowired": false, 
"secret": false, "defaultValue": false, "description": "Allows for bridging the 
consumer to the Camel routing Error Handler, which mean any exceptions occurred 
while the consumer is trying to pickup incoming messages, or the likes, will 
now be processed as a me [...]
diff --git a/components/camel-kamelet/src/main/docs/kamelet-component.adoc 
b/components/camel-kamelet/src/main/docs/kamelet-component.adoc
index 40ad18a..be9dd23 100644
--- a/components/camel-kamelet/src/main/docs/kamelet-component.adoc
+++ b/components/camel-kamelet/src/main/docs/kamelet-component.adoc
@@ -27,13 +27,14 @@ kamelet:templateId/routeId[?options]
 
 
 // component options: START
-The Kamelet component supports 7 options, which are listed below.
+The Kamelet component supports 8 options, which are listed below.
 
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
+| *location* (common) | The location of the Kamelets on the file system. | 
classpath:/kamelets | String
 | *routeProperties* (common) | Set route local parameters. |  | Map
 | *templateProperties* (common) | Set template local parameters. |  | Map
 | *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions occurred while the 
consumer is trying to pickup incoming messages, or the likes, will now be 
processed as a message and handled by the routing Error Handler. By default the 
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions, that will be logged at WARN or ERROR level and ignored. | false | 
boolean
@@ -85,9 +86,13 @@ with the following path and query parameters:
 
 [NOTE]
 ====
-The *kamelet* endpoint is *lenient*, which means that the endpoint accepts 
additional parameters that are passed to the ROute Templkate engine and 
consumed upon route materialization.
+The *kamelet* endpoint is *lenient*, which means that the endpoint accepts 
additional parameters that are passed to the 
xref:manual::route-template.adoc[Route Template] engine and consumed upon route 
materialization.
 ====
 
+== Discovery
+
+If a xref:manual::route-template.adoc[Route Template] is not found, the 
*kamelet* endpoint tries to load the related *kamelet* definition from the file 
system (by default `classpath:/kamelets`). The default resolution mechanism 
expect kamelet files to have the extension `.kamelet.yaml`.
+
 == Samples
 
 
diff --git 
a/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java
 
b/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java
index 98626ae..18aa658 100644
--- 
a/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java
+++ 
b/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/Kamelet.java
@@ -42,6 +42,7 @@ public final class Kamelet {
     public static final String SINK_ID = "sink";
     public static final String PARAM_ROUTE_ID = "routeId";
     public static final String PARAM_TEMPLATE_ID = "templateId";
+    public static final String DEFAULT_LOCATION = "classpath:/kamelets";
 
     // use a running counter as uuid
     private static final UuidGenerator UUID = new SimpleUuidGenerator();
diff --git 
a/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java
 
b/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java
index 6053bed..d7b17c3 100644
--- 
a/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java
+++ 
b/components/camel-kamelet/src/main/java/org/apache/camel/component/kamelet/KameletComponent.java
@@ -27,6 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.apache.camel.CamelContext;
 import org.apache.camel.Endpoint;
+import org.apache.camel.ExtendedCamelContext;
 import org.apache.camel.Processor;
 import org.apache.camel.RuntimeCamelException;
 import org.apache.camel.VetoCamelContextStartException;
@@ -73,6 +74,8 @@ public class KameletComponent extends DefaultComponent {
     private Map<String, Properties> templateProperties;
     @Metadata
     private Map<String, Properties> routeProperties;
+    @Metadata(defaultValue = Kamelet.DEFAULT_LOCATION)
+    private String location = Kamelet.DEFAULT_LOCATION;
 
     public KameletComponent() {
     }
@@ -263,6 +266,17 @@ public class KameletComponent extends DefaultComponent {
         this.routeProperties = routeProperties;
     }
 
+    public String getLocation() {
+        return location;
+    }
+
+    /**
+     * The location of the Kamelets on the file system.
+     */
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
     int getStateCounter() {
         return stateCounter;
     }
@@ -339,7 +353,7 @@ public class KameletComponent extends DefaultComponent {
      * Once the camel context is initialized all the endpoint tracked by this 
LifecycleHandler will
      * be used to create routes from templates.
      */
-    private static class LifecycleHandler extends LifecycleStrategySupport {
+    private class LifecycleHandler extends LifecycleStrategySupport {
         private final List<KameletEndpoint> endpoints;
         private final AtomicBoolean initialized;
 
@@ -348,19 +362,36 @@ public class KameletComponent extends DefaultComponent {
             this.initialized = new AtomicBoolean();
         }
 
-        public static void createRouteForEndpoint(KameletEndpoint endpoint) 
throws Exception {
-            LOGGER.debug("Creating route from template={} and id={}", 
endpoint.getTemplateId(), endpoint.getRouteId());
+        public void createRouteForEndpoint(KameletEndpoint endpoint) throws 
Exception {
+            final ExtendedCamelContext ecc = 
getCamelContext().adapt(ExtendedCamelContext.class);
+            final ModelCamelContext context = 
getCamelContext().adapt(ModelCamelContext.class);
+            final String templateId = endpoint.getTemplateId();
+            final String routeId = endpoint.getRouteId();
+
+            if (context.getRouteTemplateDefinition(templateId) == null) {
+                LOGGER.debug("Loading route template={} from {}", templateId, 
getLocation());
+
+                String path = getLocation();
+                if (path != null) {
+                    if (!path.endsWith("/")) {
+                        path += "/";
+                    }
+
+                    ecc.getRoutesLoader().loadRoutes(
+                            ecc.getResourceLoader().resolveResource(path + 
templateId + ".kamelet.yaml"));
+                }
+            }
+
+            LOGGER.debug("Creating route from template={} and id={}", 
templateId, routeId);
 
-            final ModelCamelContext context = 
endpoint.getCamelContext().adapt(ModelCamelContext.class);
-            final String id = 
context.addRouteFromTemplate(endpoint.getRouteId(), endpoint.getTemplateId(),
-                    endpoint.getKameletProperties());
+            final String id = context.addRouteFromTemplate(routeId, 
templateId, endpoint.getKameletProperties());
             final RouteDefinition def = context.getRouteDefinition(id);
 
             if (!def.isPrepared()) {
                 context.startRouteDefinitions(Collections.singletonList(def));
             }
 
-            LOGGER.debug("Route with id={} created from template={}", id, 
endpoint.getTemplateId());
+            LOGGER.debug("Route with id={} created from template={}", id, 
templateId);
         }
 
         @Override
diff --git 
a/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletDiscoveryTest.java
 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletDiscoveryTest.java
new file mode 100644
index 0000000..aee9463
--- /dev/null
+++ 
b/components/camel-kamelet/src/test/java/org/apache/camel/component/kamelet/KameletDiscoveryTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.component.kamelet;
+
+import org.apache.camel.FailedToCreateRouteException;
+import org.apache.camel.RoutesBuilder;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.engine.DefaultRoutesLoader;
+import org.apache.camel.spi.Resource;
+import org.apache.camel.support.RoutesBuilderLoaderSupport;
+import org.apache.camel.test.junit5.CamelTestSupport;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+public class KameletDiscoveryTest extends CamelTestSupport {
+
+    @Test
+    public void kameletCanBeDiscovered() throws Exception {
+        context.getRegistry().bind(
+                DefaultRoutesLoader.ROUTES_LOADER_KEY_PREFIX + "kamelet.yaml",
+                new RoutesBuilderLoaderSupport() {
+                    @Override
+                    public String getSupportedExtension() {
+                        return "kamelet.yaml";
+                    }
+
+                    @Override
+                    public RoutesBuilder loadRoutesBuilder(Resource resource) 
throws Exception {
+                        return new RouteBuilder() {
+                            @Override
+                            public void configure() throws Exception {
+                                routeTemplate("mySetBody")
+                                        .from("kamelet:source")
+                                        .setBody().constant("discovered");
+
+                            }
+                        };
+                    }
+                });
+
+        context.addRoutes(new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:discovery")
+                        .toF("kamelet:mySetBody");
+            }
+        });
+
+        
assertThat(fluentTemplate.to("direct:discovery").request(String.class)).isEqualTo("discovered");
+    }
+
+    @Test
+    public void kameletNotFound() throws Exception {
+        context.getRegistry().bind(
+                DefaultRoutesLoader.ROUTES_LOADER_KEY_PREFIX + "kamelet.yaml",
+                new RoutesBuilderLoaderSupport() {
+                    @Override
+                    public String getSupportedExtension() {
+                        return "kamelet.yaml";
+                    }
+
+                    @Override
+                    public RoutesBuilder loadRoutesBuilder(Resource resource) 
throws Exception {
+                        return new RouteBuilder() {
+                            @Override
+                            public void configure() throws Exception {
+                            }
+                        };
+                    }
+                });
+
+        RouteBuilder builder = new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:discovery")
+                        .toF("kamelet:mySetBody");
+            }
+        };
+
+        assertThatThrownBy(() -> context.addRoutes(builder))
+                .isInstanceOf(FailedToCreateRouteException.class)
+                .hasRootCauseMessage("Cannot find RouteTemplate with id 
mySetBody");
+    }
+}
diff --git 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
index 86c959a..e060d8f 100644
--- 
a/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
+++ 
b/core/camel-base-engine/src/main/java/org/apache/camel/impl/engine/DefaultRoutesLoader.java
@@ -84,7 +84,7 @@ public class DefaultRoutesLoader extends ServiceSupport 
implements RoutesLoader
 
         for (Resource resource : resources) {
             // the loader to use is derived from the file extension
-            final String extension = FileUtil.onlyExt(resource.getLocation(), 
true);
+            final String extension = FileUtil.onlyExt(resource.getLocation(), 
false);
 
             if (ObjectHelper.isEmpty(extension)) {
                 throw new IllegalArgumentException(
diff --git 
a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KameletComponentBuilderFactory.java
 
b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KameletComponentBuilderFactory.java
index d62f9bb..7c5a4b1 100644
--- 
a/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KameletComponentBuilderFactory.java
+++ 
b/core/camel-componentdsl/src/generated/java/org/apache/camel/builder/component/dsl/KameletComponentBuilderFactory.java
@@ -51,6 +51,21 @@ public interface KameletComponentBuilderFactory {
             extends
                 ComponentBuilder<KameletComponent> {
         /**
+         * The location of the Kamelets on the file system.
+         * 
+         * The option is a: &lt;code&gt;java.lang.String&lt;/code&gt; type.
+         * 
+         * Default: classpath:/kamelets
+         * Group: common
+         * 
+         * @param location the value to set
+         * @return the dsl builder
+         */
+        default KameletComponentBuilder location(java.lang.String location) {
+            doSetProperty("location", location);
+            return this;
+        }
+        /**
          * Set route local parameters.
          * 
          * The option is a: &lt;code&gt;java.util.Map&amp;lt;java.lang.String,
@@ -198,6 +213,7 @@ public interface KameletComponentBuilderFactory {
                 String name,
                 Object value) {
             switch (name) {
+            case "location": ((KameletComponent) 
component).setLocation((java.lang.String) value); return true;
             case "routeProperties": ((KameletComponent) 
component).setRouteProperties((java.util.Map) value); return true;
             case "templateProperties": ((KameletComponent) 
component).setTemplateProperties((java.util.Map) value); return true;
             case "bridgeErrorHandler": ((KameletComponent) 
component).setBridgeErrorHandler((boolean) value); return true;
diff --git a/docs/components/modules/ROOT/pages/kamelet-component.adoc 
b/docs/components/modules/ROOT/pages/kamelet-component.adoc
index 26a21ff..4357148 100644
--- a/docs/components/modules/ROOT/pages/kamelet-component.adoc
+++ b/docs/components/modules/ROOT/pages/kamelet-component.adoc
@@ -29,13 +29,14 @@ kamelet:templateId/routeId[?options]
 
 
 // component options: START
-The Kamelet component supports 7 options, which are listed below.
+The Kamelet component supports 8 options, which are listed below.
 
 
 
 [width="100%",cols="2,5,^1,2",options="header"]
 |===
 | Name | Description | Default | Type
+| *location* (common) | The location of the Kamelets on the file system. | 
classpath:/kamelets | String
 | *routeProperties* (common) | Set route local parameters. |  | Map
 | *templateProperties* (common) | Set template local parameters. |  | Map
 | *bridgeErrorHandler* (consumer) | Allows for bridging the consumer to the 
Camel routing Error Handler, which mean any exceptions occurred while the 
consumer is trying to pickup incoming messages, or the likes, will now be 
processed as a message and handled by the routing Error Handler. By default the 
consumer will use the org.apache.camel.spi.ExceptionHandler to deal with 
exceptions, that will be logged at WARN or ERROR level and ignored. | false | 
boolean
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
index cb20da3..f0a42dc 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializationContext.java
@@ -87,6 +87,10 @@ public class YamlDeserializationContext extends 
StandardConstructor implements C
         this.camelContext = camelContext.adapt(ExtendedCamelContext.class);
     }
 
+    public Object constructDocument(Node node) {
+        return super.construct(node);
+    }
+
     @Override
     protected Optional<ConstructNode> findConstructorFor(Node node) {
         ConstructNode ctor = resolve(node);
@@ -141,6 +145,19 @@ public class YamlDeserializationContext extends 
StandardConstructor implements C
         return type.cast(result);
     }
 
+    public <T> T construct(Node node, Class<T> type) {
+        ConstructNode constructor = resolve(type);
+        if (constructor == null) {
+            throw new YamlDeserializationException("Unable to find constructor 
for node: " + node);
+        }
+        Object result = constructor.construct(node);
+        if (result == null) {
+            return null;
+        }
+
+        return type.cast(result);
+    }
+
     // *********************************
     //
     // Resolve
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializerSupport.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializerSupport.java
index 48cade8..8ddd141 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializerSupport.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl-common/src/main/java/org/apache/camel/dsl/yaml/common/YamlDeserializerSupport.java
@@ -78,14 +78,26 @@ public class YamlDeserializerSupport {
     }
 
     public static byte[] asByteArray(Node node) {
+        if (node == null) {
+            return null;
+        }
+
         return asByteArray(asText(node));
     }
 
     public static Class<?> asClass(Node node) {
+        if (node == null) {
+            return null;
+        }
+
         return asClass(asText(node));
     }
 
     public static List<String> asStringList(Node node) {
+        if (node == null) {
+            return null;
+        }
+
         List<String> answer;
 
         if (node.getNodeType() == NodeType.SCALAR) {
@@ -103,14 +115,25 @@ public class YamlDeserializerSupport {
     }
 
     public static Set<String> asStringSet(Node node) {
+        if (node == null) {
+            return null;
+        }
+
         return asStringSet(asText(node));
     }
 
     public static Class<?>[] asClassArray(Node node) throws 
YamlDeserializationException {
+        if (node == null) {
+            return null;
+        }
+
         return asClassArray(asText(node));
     }
 
     public static String asText(Node node) throws YamlDeserializationException 
{
+        if (node == null) {
+            return null;
+        }
         if (node.getNodeType() != NodeType.SCALAR) {
             throw new IllegalArgumentException("Node is not SCALAR");
         }
@@ -119,6 +142,10 @@ public class YamlDeserializerSupport {
     }
 
     public static Map<String, Object> asMap(Node node) {
+        if (node == null) {
+            return null;
+        }
+
         final MappingNode mn = asMappingNode(node);
         final Map<String, Object> answer = new HashMap<>();
 
@@ -142,6 +169,10 @@ public class YamlDeserializerSupport {
     }
 
     public static Map<String, Object> asScalarMap(Node node) {
+        if (node == null) {
+            return null;
+        }
+
         final MappingNode mn = asMappingNode(node);
         final Map<String, Object> answer = new HashMap<>();
 
@@ -352,4 +383,27 @@ public class YamlDeserializerSupport {
             }
         }
     }
+
+    public static Node nodeAt(Node root, String pointer) {
+        if (ObjectHelper.isEmpty(pointer)) {
+            return root;
+        }
+
+        MappingNode mn = asMappingNode(root);
+        for (String path : pointer.split("/")) {
+            for (NodeTuple child : mn.getValue()) {
+                if (child.getKeyNode() instanceof ScalarNode) {
+                    ScalarNode scalar = (ScalarNode) child.getKeyNode();
+                    if (scalar.getValue().equals(path)) {
+                        String next = pointer.substring(path.length() + 1);
+                        return ObjectHelper.isEmpty(next)
+                                ? child.getValueNode()
+                                : nodeAt(child.getValueNode(), next);
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
 }
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/META-INF/services/org/apache/camel/routes-loader/kamelet.yaml
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/META-INF/services/org/apache/camel/routes-loader/kamelet.yaml
new file mode 100644
index 0000000..867db7d
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/META-INF/services/org/apache/camel/routes-loader/kamelet.yaml
@@ -0,0 +1,2 @@
+# Generated by camel build tools - do NOT edit this file!
+class=org.apache.camel.dsl.yaml.KameletRoutesBuilderLoader
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/KameletRoutesBuilderLoader.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/KameletRoutesBuilderLoader.java
new file mode 100644
index 0000000..9502a8b
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/KameletRoutesBuilderLoader.java
@@ -0,0 +1,73 @@
+/*
+ * 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.yaml;
+
+import org.apache.camel.api.management.ManagedResource;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.model.RouteTemplateDefinition;
+import org.apache.camel.spi.annotations.RoutesLoader;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.nodes.NodeTuple;
+
+import static 
org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asMappingNode;
+import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asText;
+import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.nodeAt;
+
+@ManagedResource(description = "Managed Kamelet RoutesBuilderLoader")
+@RoutesLoader(KameletRoutesBuilderLoader.EXTENSION)
+public class KameletRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport 
{
+    public static final String EXTENSION = "kamelet.yaml";
+
+    public KameletRoutesBuilderLoader() {
+        super(EXTENSION);
+    }
+
+    @Override
+    protected RouteBuilder builder(Node node) {
+        Node template = nodeAt(node, "/spec/template");
+        if (template == null) {
+            // fallback till flow get removed
+            template = nodeAt(node, "/spec/flow");
+        }
+        if (template == null) {
+            throw new IllegalArgumentException("No template defined");
+        }
+
+        final YamlDeserializationContext context = 
this.getDeserializationContext();
+        final RouteTemplateDefinition rtd = context.construct(template, 
RouteTemplateDefinition.class);
+
+        rtd.id(asText(nodeAt(node, "/metadata/name")));
+
+        Node properties = nodeAt(node, "/spec/definition/properties");
+        if (properties != null) {
+            for (NodeTuple p : asMappingNode(properties).getValue()) {
+                final String key = asText(p.getKeyNode());
+                final Node def = nodeAt(p.getValueNode(), "/default");
+
+                rtd.templateParameter(key, asText(def));
+            }
+        }
+
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                getRouteTemplateCollection().routeTemplate(rtd);
+            }
+        };
+    }
+}
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java
index f58e24f..2d19c50 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoader.java
@@ -16,20 +16,9 @@
  */
 package org.apache.camel.dsl.yaml;
 
-import java.io.InputStream;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
 import org.apache.camel.api.management.ManagedResource;
 import org.apache.camel.builder.ErrorHandlerBuilder;
 import org.apache.camel.builder.RouteBuilder;
-import org.apache.camel.dsl.support.RouteBuilderLoaderSupport;
-import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
-import org.apache.camel.dsl.yaml.common.YamlDeserializationMode;
-import org.apache.camel.dsl.yaml.deserializers.CustomResolver;
-import 
org.apache.camel.dsl.yaml.deserializers.EndpointProducerDeserializersResolver;
-import org.apache.camel.dsl.yaml.deserializers.ModelDeserializersResolver;
 import org.apache.camel.dsl.yaml.deserializers.OutputAwareFromDefinition;
 import org.apache.camel.model.OnExceptionDefinition;
 import org.apache.camel.model.RouteDefinition;
@@ -37,109 +26,57 @@ import org.apache.camel.model.RouteTemplateDefinition;
 import org.apache.camel.model.rest.RestDefinition;
 import org.apache.camel.model.rest.VerbDefinition;
 import org.apache.camel.spi.CamelContextCustomizer;
-import org.apache.camel.spi.Resource;
 import org.apache.camel.spi.annotations.RoutesLoader;
-import org.apache.camel.support.service.ServiceHelper;
-import org.apache.camel.util.ObjectHelper;
-import org.snakeyaml.engine.v2.api.Load;
-import org.snakeyaml.engine.v2.api.LoadSettings;
+import org.snakeyaml.engine.v2.nodes.Node;
+
+import static 
org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asSequenceNode;
 
 @ManagedResource(description = "Managed YAML RoutesBuilderLoader")
 @RoutesLoader(YamlRoutesBuilderLoader.EXTENSION)
-public class YamlRoutesBuilderLoader extends RouteBuilderLoaderSupport {
-    public static final String DESERIALIZATION_MODE = 
"CamelYamlDslDeserializationMode";
+public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
     public static final String EXTENSION = "yaml";
 
-    private LoadSettings settings;
-    private YamlDeserializationContext deserializationContext;
-
     public YamlRoutesBuilderLoader() {
         super(EXTENSION);
     }
 
-    @Override
-    protected void doBuild() throws Exception {
-        super.doBuild();
-
-        this.settings = LoadSettings.builder().build();
-        this.deserializationContext = new YamlDeserializationContext(settings);
-        this.deserializationContext.setCamelContext(getCamelContext());
-        this.deserializationContext.addResolvers(new CustomResolver());
-        this.deserializationContext.addResolvers(new 
ModelDeserializersResolver());
-        this.deserializationContext.addResolvers(new 
EndpointProducerDeserializersResolver());
-    }
-
-    @Override
-    protected void doStart() throws Exception {
-        super.doStart();
-
-        final Map<String, String> options = 
getCamelContext().getGlobalOptions();
-        final String mode = options.getOrDefault(DESERIALIZATION_MODE, 
YamlDeserializationMode.CLASSIC.name());
-        if (mode != null) {
-            this.deserializationContext.setDeserializationMode(
-                    
YamlDeserializationMode.valueOf(mode.toUpperCase(Locale.US)));
-        }
-
-        ServiceHelper.startService(this.deserializationContext);
-    }
-
-    @Override
-    protected void doStop() throws Exception {
-        super.doStop();
-
-        ServiceHelper.stopService(this.deserializationContext);
-
-        this.deserializationContext = null;
-        this.settings = null;
-    }
-
-    @Override
-    public RouteBuilder doLoadRouteBuilder(Resource resource) throws Exception 
{
-        ObjectHelper.notNull(deserializationContext, "constructor");
-        ObjectHelper.notNull(settings, "settings");
-
+    protected RouteBuilder builder(Node root) {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
-                final Load load = new Load(settings, deserializationContext);
-
-                try (InputStream is = resource.getInputStream()) {
-                    for (Object item : (List<?>) load.loadFromInputStream(is)) 
{
-
-                        configure(item);
-                    }
-                }
-            }
-
-            private void configure(Object item) {
-                if (item instanceof OutputAwareFromDefinition) {
-                    RouteDefinition route = new RouteDefinition();
-                    route.setInput(((OutputAwareFromDefinition) 
item).getDelegate());
-                    route.setOutputs(((OutputAwareFromDefinition) 
item).getOutputs());
-                    getRouteCollection().route(route);
-                } else if (item instanceof RouteDefinition) {
-                    getRouteCollection().route((RouteDefinition) item);
-                } else if (item instanceof RestDefinition) {
-                    RestDefinition definition = (RestDefinition) item;
-                    for (VerbDefinition verb : definition.getVerbs()) {
-                        verb.setRest(definition);
-                    }
-                    getRestCollection().rest(definition);
-                } else if (item instanceof CamelContextCustomizer) {
-                    ((CamelContextCustomizer) 
item).configure(getCamelContext());
-                } else if (item instanceof OnExceptionDefinition) {
-                    if (!getRouteCollection().getRoutes().isEmpty()) {
-                        throw new IllegalArgumentException("onException must 
be defined before any routes in the RouteBuilder");
-                    }
-                    
getRouteCollection().getOnExceptions().add((OnExceptionDefinition) item);
-                } else if (item instanceof ErrorHandlerBuilder) {
-                    if (!getRouteCollection().getRoutes().isEmpty()) {
-                        throw new IllegalArgumentException(
-                                "errorHandler must be defined before any 
routes in the RouteBuilder");
+                for (Node node : asSequenceNode(root).getValue()) {
+                    Object item = 
getDeserializationContext().mandatoryResolve(node).construct(node);
+
+                    if (item instanceof OutputAwareFromDefinition) {
+                        RouteDefinition route = new RouteDefinition();
+                        route.setInput(((OutputAwareFromDefinition) 
item).getDelegate());
+                        route.setOutputs(((OutputAwareFromDefinition) 
item).getOutputs());
+                        getRouteCollection().route(route);
+                    } else if (item instanceof RouteDefinition) {
+                        getRouteCollection().route((RouteDefinition) item);
+                    } else if (item instanceof RestDefinition) {
+                        RestDefinition definition = (RestDefinition) item;
+                        for (VerbDefinition verb : definition.getVerbs()) {
+                            verb.setRest(definition);
+                        }
+                        getRestCollection().rest(definition);
+                    } else if (item instanceof CamelContextCustomizer) {
+                        ((CamelContextCustomizer) 
item).configure(getCamelContext());
+                    } else if (item instanceof OnExceptionDefinition) {
+                        if (!getRouteCollection().getRoutes().isEmpty()) {
+                            throw new IllegalArgumentException(
+                                    "onException must be defined before any 
routes in the RouteBuilder");
+                        }
+                        
getRouteCollection().getOnExceptions().add((OnExceptionDefinition) item);
+                    } else if (item instanceof ErrorHandlerBuilder) {
+                        if (!getRouteCollection().getRoutes().isEmpty()) {
+                            throw new IllegalArgumentException(
+                                    "errorHandler must be defined before any 
routes in the RouteBuilder");
+                        }
+                        errorHandler((ErrorHandlerBuilder) item);
+                    } else if (item instanceof RouteTemplateDefinition) {
+                        
getRouteTemplateCollection().routeTemplate((RouteTemplateDefinition) item);
                     }
-                    errorHandler((ErrorHandlerBuilder) item);
-                } else if (item instanceof RouteTemplateDefinition) {
-                    
getRouteTemplateCollection().routeTemplate((RouteTemplateDefinition) item);
                 }
             }
         };
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.java
new file mode 100644
index 0000000..9f8078c
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/main/java/org/apache/camel/dsl/yaml/YamlRoutesBuilderLoaderSupport.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.yaml;
+
+import java.io.InputStream;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dsl.support.RouteBuilderLoaderSupport;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationContext;
+import org.apache.camel.dsl.yaml.common.YamlDeserializationMode;
+import org.apache.camel.dsl.yaml.common.exception.YamlDeserializationException;
+import org.apache.camel.dsl.yaml.deserializers.CustomResolver;
+import 
org.apache.camel.dsl.yaml.deserializers.EndpointProducerDeserializersResolver;
+import org.apache.camel.dsl.yaml.deserializers.ModelDeserializersResolver;
+import org.apache.camel.spi.Resource;
+import org.apache.camel.support.service.ServiceHelper;
+import org.apache.camel.util.ObjectHelper;
+import org.snakeyaml.engine.v2.api.LoadSettings;
+import org.snakeyaml.engine.v2.api.YamlUnicodeReader;
+import org.snakeyaml.engine.v2.composer.Composer;
+import org.snakeyaml.engine.v2.nodes.Node;
+import org.snakeyaml.engine.v2.parser.Parser;
+import org.snakeyaml.engine.v2.parser.ParserImpl;
+import org.snakeyaml.engine.v2.scanner.StreamReader;
+
+abstract class YamlRoutesBuilderLoaderSupport extends 
RouteBuilderLoaderSupport {
+    public static final String DESERIALIZATION_MODE = 
"CamelYamlDslDeserializationMode";
+
+    private LoadSettings settings;
+    private YamlDeserializationContext deserializationContext;
+    private YamlDeserializationMode deserializationMode;
+
+    public YamlRoutesBuilderLoaderSupport(String extension) {
+        super(extension);
+    }
+
+    public YamlDeserializationMode getDeserializationMode() {
+        return deserializationMode;
+    }
+
+    public void setDeserializationMode(YamlDeserializationMode 
deserializationMode) {
+        this.deserializationMode = deserializationMode;
+    }
+
+    @Override
+    protected void doBuild() throws Exception {
+        super.doBuild();
+
+        this.settings = LoadSettings.builder().build();
+        this.deserializationContext = new YamlDeserializationContext(settings);
+        this.deserializationContext.setCamelContext(getCamelContext());
+        this.deserializationContext.addResolvers(new CustomResolver());
+        this.deserializationContext.addResolvers(new 
ModelDeserializersResolver());
+        this.deserializationContext.addResolvers(new 
EndpointProducerDeserializersResolver());
+    }
+
+    @Override
+    protected void doStart() throws Exception {
+        super.doStart();
+
+        if (this.deserializationMode == null) {
+            final Map<String, String> options = 
getCamelContext().getGlobalOptions();
+            final String mode = options.getOrDefault(DESERIALIZATION_MODE, 
YamlDeserializationMode.CLASSIC.name());
+            if (mode != null) {
+                this.deserializationContext.setDeserializationMode(
+                        
YamlDeserializationMode.valueOf(mode.toUpperCase(Locale.US)));
+            }
+        } else {
+            
this.deserializationContext.setDeserializationMode(deserializationMode);
+        }
+
+        ServiceHelper.startService(this.deserializationContext);
+    }
+
+    @Override
+    protected void doStop() throws Exception {
+        super.doStop();
+
+        ServiceHelper.stopService(this.deserializationContext);
+
+        this.deserializationContext = null;
+        this.settings = null;
+    }
+
+    @Override
+    public RouteBuilder doLoadRouteBuilder(Resource resource) throws Exception 
{
+        ObjectHelper.notNull(deserializationContext, "constructor");
+        ObjectHelper.notNull(settings, "settings");
+
+        try (InputStream is = resource.getInputStream()) {
+            final StreamReader reader = new StreamReader(new 
YamlUnicodeReader(is), settings);
+            final Parser parser = new ParserImpl(reader, settings);
+            final Composer composer = new Composer(parser, settings);
+
+            return composer.getSingleNode()
+                    .map(this::builder)
+                    .orElseThrow(() -> new 
YamlDeserializationException("Unable to deserialize resource"));
+        }
+    }
+
+    protected LoadSettings getSettings() {
+        return this.settings;
+    }
+
+    protected YamlDeserializationContext getDeserializationContext() {
+        return this.deserializationContext;
+    }
+
+    protected abstract RouteBuilder builder(Node node);
+}
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletLoaderTest.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletLoaderTest.groovy
new file mode 100644
index 0000000..d3ae630
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletLoaderTest.groovy
@@ -0,0 +1,208 @@
+/*
+ * 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.yaml
+
+import org.apache.camel.component.mock.MockEndpoint
+import org.apache.camel.dsl.yaml.support.YamlTestSupport
+import org.apache.camel.model.ToDefinition
+
+class KameletLoaderTest extends YamlTestSupport {
+    @Override
+    def doSetup() {
+        context.start()
+    }
+
+    def "kamelet with flow"() {
+        when:
+            loadKamelets('''
+                apiVersion: camel.apache.org/v1alpha1
+                kind: Kamelet
+                metadata:
+                  name: aws-s3-sink                  
+                spec:
+                  definition:
+                    title: "AWS S3 Sink"
+                    description: "AWS S3 Sink"
+                    required:
+                      - bucketNameOrArn
+                      - accessKey
+                      - secretKey
+                      - region
+                    type: object
+                    properties:
+                      bucketNameOrArn:
+                        title: Bucket Name
+                        description: The S3 Bucket name or ARN.
+                        type: string
+                      accessKey:
+                        title: Access Key
+                        description: The access key obtained from AWS.
+                        type: string
+                        format: password
+                        x-descriptors:
+                        - urn:alm:descriptor:com.tectonic.ui:password
+                      overrideEndpoint:
+                        title: Override Endpoint
+                        type: boolean
+                        default: false
+                        x-descriptors:
+                        - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+                  flow:
+                    from:
+                      uri: "kamelet:source"
+                      steps:
+                      - to:
+                          uri: "aws2-s3:{{bucketNameOrArn}}"
+                          parameters:
+                            secretKey: "{{secretKey}}"
+                            accessKey: "{{accessKey}}"
+                            region: "{{region}}"
+                            uriEndpointOverride: "{{uriEndpointOverride}}"
+                            overrideEndpoint: "{{overrideEndpoint}}"
+                            autoCreateBucket: "{{autoCreateBucket}}"
+            ''')
+        then:
+            context.routeTemplateDefinitions.size() == 1
+
+            with (context.routeTemplateDefinitions[0]) {
+                id == 'aws-s3-sink'
+
+                templateParameters.size() == 3
+
+                templateParameters.any {
+                    it.name == 'bucketNameOrArn' && it.defaultValue == null
+                }
+                templateParameters.any {
+                    it.name == 'overrideEndpoint' && it.defaultValue == 'false'
+                }
+
+                with(route) {
+                    input.endpointUri == 'kamelet:source'
+                    outputs.size() == 1
+                    with (outputs[0], ToDefinition) {
+                        endpointUri ==~ /aws2-s3:.*/
+                    }
+                }
+            }
+    }
+
+    def "kamelet with template"() {
+        when:
+            loadKamelets('''
+                apiVersion: camel.apache.org/v1alpha1
+                kind: Kamelet
+                metadata:
+                  name: aws-s3-sink                  
+                spec:
+                  definition:
+                    title: "AWS S3 Sink"
+                    description: "AWS S3 Sink"
+                    required:
+                      - bucketNameOrArn
+                      - accessKey
+                      - secretKey
+                      - region
+                    type: object
+                    properties:
+                      bucketNameOrArn:
+                        title: Bucket Name
+                        description: The S3 Bucket name or ARN.
+                        type: string
+                      accessKey:
+                        title: Access Key
+                        description: The access key obtained from AWS.
+                        type: string
+                        format: password
+                        x-descriptors:
+                        - urn:alm:descriptor:com.tectonic.ui:password
+                      overrideEndpoint:
+                        title: Override Endpoint
+                        type: boolean
+                        default: false
+                        x-descriptors:
+                        - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+                  template:
+                    from:
+                      uri: "kamelet:source"
+                      steps:
+                      - to:
+                          uri: "aws2-s3:{{bucketNameOrArn}}"
+                          parameters:
+                            secretKey: "{{secretKey}}"
+                            accessKey: "{{accessKey}}"
+                            region: "{{region}}"
+                            uriEndpointOverride: "{{uriEndpointOverride}}"
+                            overrideEndpoint: "{{overrideEndpoint}}"
+                            autoCreateBucket: "{{autoCreateBucket}}"
+            ''')
+        then:
+            context.routeTemplateDefinitions.size() == 1
+
+            with (context.routeTemplateDefinitions[0]) {
+                id == 'aws-s3-sink'
+
+                templateParameters.size() == 3
+
+                templateParameters.any {
+                    it.name == 'bucketNameOrArn' && it.defaultValue == null
+                }
+                templateParameters.any {
+                    it.name == 'overrideEndpoint' && it.defaultValue == 'false'
+                }
+
+                with(route) {
+                    input.endpointUri == 'kamelet:source'
+                    outputs.size() == 1
+                    with (outputs[0], ToDefinition) {
+                        endpointUri ==~ /aws2-s3:.*/
+                    }
+                }
+            }
+    }
+
+    def "kamelet discovery"() {
+        setup:
+            def payload = UUID.randomUUID().toString()
+
+            loadRoutes """
+                - from:
+                    uri: "direct:start"
+                    steps:
+                      - to: "kamelet:mySetBody?payload=${payload}"
+                      - to: "mock:result"
+            """
+
+            withMock('mock:result') {
+                expectedMessageCount 1
+                expectedBodiesReceived payload
+            }
+        when:
+            context.start()
+
+            withTemplate {
+                to('direct:start').withBody(payload).send()
+            }
+        then:
+            context.routeTemplateDefinitions.size() == 1
+
+            with (context.routeTemplateDefinitions[0]) {
+                id == 'mySetBody'
+            }
+
+            MockEndpoint.assertIsSatisfied(context)
+    }
+}
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy
index bbffe8d..a9749f8 100644
--- 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/support/YamlTestSupport.groovy
@@ -52,7 +52,6 @@ class YamlTestSupport extends Specification implements 
HasCamelContext {
             def report = SCHEMA.validate(target)
 
             if (!report.isSuccess()) {
-
                 throw new IllegalArgumentException("${report}")
             }
         }
@@ -85,6 +84,16 @@ class YamlTestSupport extends Specification implements 
HasCamelContext {
         )
     }
 
+    def loadKamelets(String... resources) {
+        int index = 0
+
+        context.routesLoader.loadRoutes(
+            resources.collect {
+                it -> 
ResourceHelper.fromString("route-${index++}.kamelet.yaml", it.stripIndent())
+            }
+        )
+    }
+
     def withMock(
             String uri,
             @DelegatesTo(MockEndpoint) Closure<?> closure) {
diff --git 
a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/mySetBody.kamelet.yaml
 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/mySetBody.kamelet.yaml
new file mode 100644
index 0000000..fa5f49d
--- /dev/null
+++ 
b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/mySetBody.kamelet.yaml
@@ -0,0 +1,32 @@
+#
+# 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.
+#
+apiVersion: camel.apache.org/v1alpha1
+kind: Kamelet
+metadata:
+  name: mySetBody
+spec:
+  definition:
+    properties:
+      payload:
+        title: The Payload
+        type: string
+  template:
+    from:
+      uri: "kamelet:source"
+      steps:
+        - set-body:
+            constant: "{{payload}}"
\ No newline at end of file

Reply via email to