You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2021/12/07 13:51:52 UTC

[camel] 01/08: CAMEL-17261: camel-yaml-dsl - Add support for loading Camel K KameletBinding file. WIP.

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

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

commit 30b2fbc7b761231bf6ed5f2e38156d49e00a96b9
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Tue Dec 7 07:03:17 2021 +0100

    CAMEL-17261: camel-yaml-dsl - Add support for loading Camel K KameletBinding file. WIP.
---
 .../dsl/yaml/deserializers/ModelDeserializers.java |  37 ---
 .../deserializers/ModelDeserializersResolver.java  |   3 -
 dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml          |  10 +
 .../src/generated/resources/camel-yaml-dsl.json    |   9 -
 .../camel/dsl/yaml/YamlRoutesBuilderLoader.java    | 269 ++++++++++++-----
 .../dsl/yaml/YamlRoutesBuilderLoaderSupport.java   |  37 +++
 .../camel/dsl/yaml/KameletBindingLoaderTest.groovy | 335 +++++++++++++++++++++
 .../camel/dsl/yaml/support/YamlTestSupport.groovy  |  22 ++
 .../test/resources/kamelets/log-sink.kamelet.yaml  |  61 ++++
 .../resources/kamelets/prefix-action.kamelet.yaml  |  45 +++
 .../resources/kamelets/timer-source.kamelet.yaml   |  69 +++++
 11 files changed, 776 insertions(+), 121 deletions(-)

diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
index 1804ce1..a6eb0f8 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializers.java
@@ -69,7 +69,6 @@ import org.apache.camel.model.Resilience4jConfigurationDefinition;
 import org.apache.camel.model.RestContextRefDefinition;
 import org.apache.camel.model.RollbackDefinition;
 import org.apache.camel.model.RouteBuilderDefinition;
-import org.apache.camel.model.RouteConfigurationContextRefDefinition;
 import org.apache.camel.model.RouteContextRefDefinition;
 import org.apache.camel.model.RouteDefinition;
 import org.apache.camel.model.RouteTemplateParameterDefinition;
@@ -12919,42 +12918,6 @@ public final class ModelDeserializers extends YamlDeserializerSupport {
     }
 
     @YamlType(
-            types = org.apache.camel.model.RouteConfigurationContextRefDefinition.class,
-            order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1,
-            nodes = {
-                    "route-configuration-context-ref",
-                    "routeConfigurationContextRef"
-            },
-            properties = @YamlProperty(name = "ref", type = "string", required = true)
-    )
-    public static class RouteConfigurationContextRefDefinitionDeserializer extends YamlDeserializerBase<RouteConfigurationContextRefDefinition> {
-        public RouteConfigurationContextRefDefinitionDeserializer() {
-            super(RouteConfigurationContextRefDefinition.class);
-        }
-
-        @Override
-        protected RouteConfigurationContextRefDefinition newInstance() {
-            return new RouteConfigurationContextRefDefinition();
-        }
-
-        @Override
-        protected boolean setProperty(RouteConfigurationContextRefDefinition target,
-                String propertyKey, String propertyName, Node node) {
-            switch(propertyKey) {
-                case "ref": {
-                    String val = asText(node);
-                    target.setRef(val);
-                    break;
-                }
-                default: {
-                    return false;
-                }
-            }
-            return true;
-        }
-    }
-
-    @YamlType(
             types = org.apache.camel.model.RouteContextRefDefinition.class,
             order = org.apache.camel.dsl.yaml.common.YamlDeserializerResolver.ORDER_LOWEST - 1,
             nodes = {
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java
index f0629b1..f5837a5 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl-deserializers/src/generated/java/org/apache/camel/dsl/yaml/deserializers/ModelDeserializersResolver.java
@@ -363,9 +363,6 @@ public final class ModelDeserializersResolver implements YamlDeserializerResolve
             case "route-builder": return new ModelDeserializers.RouteBuilderDefinitionDeserializer();
             case "routeBuilder": return new ModelDeserializers.RouteBuilderDefinitionDeserializer();
             case "org.apache.camel.model.RouteBuilderDefinition": return new ModelDeserializers.RouteBuilderDefinitionDeserializer();
-            case "route-configuration-context-ref": return new ModelDeserializers.RouteConfigurationContextRefDefinitionDeserializer();
-            case "routeConfigurationContextRef": return new ModelDeserializers.RouteConfigurationContextRefDefinitionDeserializer();
-            case "org.apache.camel.model.RouteConfigurationContextRefDefinition": return new ModelDeserializers.RouteConfigurationContextRefDefinitionDeserializer();
             case "route-context-ref": return new ModelDeserializers.RouteContextRefDefinitionDeserializer();
             case "routeContextRef": return new ModelDeserializers.RouteContextRefDefinitionDeserializer();
             case "org.apache.camel.model.RouteContextRefDefinition": return new ModelDeserializers.RouteContextRefDefinitionDeserializer();
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml b/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml
index 787cfb2..7170e1d4 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/pom.xml
@@ -116,6 +116,16 @@
         </dependency>
         <dependency>
             <groupId>org.apache.camel</groupId>
+            <artifactId>camel-timer</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
+            <artifactId>camel-stub</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.camel</groupId>
             <artifactId>camel-log</artifactId>
             <scope>test</scope>
         </dependency>
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
index 62e992b..89dfbf3 100644
--- a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/generated/resources/camel-yaml-dsl.json
@@ -2131,15 +2131,6 @@
         } ],
         "required" : [ "ref" ]
       },
-      "org.apache.camel.model.RouteConfigurationContextRefDefinition" : {
-        "type" : "object",
-        "properties" : {
-          "ref" : {
-            "type" : "string"
-          }
-        },
-        "required" : [ "ref" ]
-      },
       "org.apache.camel.model.RouteConfigurationDefinition" : {
         "oneOf" : [ {
           "type" : "string"
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 30cd3fa..fa7ca59 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,11 +16,12 @@
  */
 package org.apache.camel.dsl.yaml;
 
-import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 import org.apache.camel.CamelContextAware;
 import org.apache.camel.api.management.ManagedResource;
+import org.apache.camel.builder.DeadLetterChannelBuilder;
 import org.apache.camel.builder.ErrorHandlerBuilder;
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.builder.RouteConfigurationBuilder;
@@ -35,11 +36,15 @@ 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.annotations.RoutesLoader;
+import org.apache.camel.util.URISupport;
 import org.snakeyaml.engine.v2.nodes.MappingNode;
 import org.snakeyaml.engine.v2.nodes.Node;
 import org.snakeyaml.engine.v2.nodes.NodeTuple;
 import org.snakeyaml.engine.v2.nodes.NodeType;
+import org.snakeyaml.engine.v2.nodes.SequenceNode;
 
+import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asMap;
+import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asMappingNode;
 import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asSequenceNode;
 import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asText;
 import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.nodeAt;
@@ -47,8 +52,15 @@ import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.nodeAt;
 @ManagedResource(description = "Managed YAML RoutesBuilderLoader")
 @RoutesLoader(YamlRoutesBuilderLoader.EXTENSION)
 public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
+
     public static final String EXTENSION = "yaml";
 
+    // API versions for Camel-K Integration and Kamelet Binding
+    // we are lenient so lets just assume we can work with any of the v1 even if they evolve
+    private static final String INTEGRATION_VERSION = "camel.apache.org/v1";
+    private static final String BINDING_VERSION = "camel.apache.org/v1";
+    private static final String STRIMZI_VERSION = "kafka.strimzi.io/v1";
+
     public YamlRoutesBuilderLoader() {
         super(EXTENSION);
     }
@@ -57,103 +69,216 @@ public class YamlRoutesBuilderLoader extends YamlRoutesBuilderLoaderSupport {
         return new RouteConfigurationBuilder() {
             @Override
             public void configure() throws Exception {
-                Node target = preConfigureNode(root);
-
-                for (Node node : asSequenceNode(target).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());
-
-                        CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
-                        getRouteCollection().route(route);
-                    } else if (item instanceof RouteDefinition) {
-                        CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
-                        getRouteCollection().route((RouteDefinition) item);
-                    } 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");
-                        }
-                        CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
-                        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) {
-                        CamelContextAware.trySetCamelContext(getRouteTemplateCollection(), getCamelContext());
-                        getRouteTemplateCollection().routeTemplate((RouteTemplateDefinition) item);
-                    } else if (item instanceof RestDefinition) {
-                        RestDefinition definition = (RestDefinition) item;
-                        for (VerbDefinition verb : definition.getVerbs()) {
-                            verb.setRest(definition);
-                        }
-                        CamelContextAware.trySetCamelContext(getRestCollection(), getCamelContext());
-                        getRestCollection().rest(definition);
-                    } else if (item instanceof RestConfigurationDefinition) {
-                        ((RestConfigurationDefinition) item).asRestConfiguration(
-                                getCamelContext(),
-                                getCamelContext().getRestConfiguration());
+                Object target = preConfigureNode(root);
+                if (target == null) {
+                    return;
+                }
+
+                if (target instanceof Node) {
+                    SequenceNode seq = asSequenceNode((Node) target);
+                    for (Node node : seq.getValue()) {
+                        Object item = getDeserializationContext().mandatoryResolve(node).construct(node);
+                        doConfigure(item);
+                    }
+                } else {
+                    doConfigure(target);
+                }
+            }
+
+            private void doConfigure(Object item) throws Exception {
+                if (item instanceof OutputAwareFromDefinition) {
+                    RouteDefinition route = new RouteDefinition();
+                    route.setInput(((OutputAwareFromDefinition) item).getDelegate());
+                    route.setOutputs(((OutputAwareFromDefinition) item).getOutputs());
+
+                    CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
+                    getRouteCollection().route(route);
+                } else if (item instanceof RouteDefinition) {
+                    CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
+                    getRouteCollection().route((RouteDefinition) item);
+                } 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");
+                    }
+                    CamelContextAware.trySetCamelContext(getRouteCollection(), getCamelContext());
+                    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) {
+                    CamelContextAware.trySetCamelContext(getRouteTemplateCollection(), getCamelContext());
+                    getRouteTemplateCollection().routeTemplate((RouteTemplateDefinition) item);
+                } else if (item instanceof RestDefinition) {
+                    RestDefinition definition = (RestDefinition) item;
+                    for (VerbDefinition verb : definition.getVerbs()) {
+                        verb.setRest(definition);
                     }
+                    CamelContextAware.trySetCamelContext(getRestCollection(), getCamelContext());
+                    getRestCollection().rest(definition);
+                } else if (item instanceof RestConfigurationDefinition) {
+                    ((RestConfigurationDefinition) item).asRestConfiguration(
+                            getCamelContext(),
+                            getCamelContext().getRestConfiguration());
                 }
             }
 
             @Override
             public void configuration() throws Exception {
-                Node target = preConfigureNode(root);
+                Object target = preConfigureNode(root);
+                if (target == null) {
+                    return;
+                }
 
-                for (Node node : asSequenceNode(target).getValue()) {
-                    Object item = getDeserializationContext().mandatoryResolve(node).construct(node);
-                    if (item instanceof RouteConfigurationDefinition) {
-                        CamelContextAware.trySetCamelContext(getRouteConfigurationCollection(), getCamelContext());
-                        getRouteConfigurationCollection().routeConfiguration((RouteConfigurationDefinition) item);
+                if (target instanceof Node) {
+                    SequenceNode seq = asSequenceNode((Node) target);
+                    for (Node node : seq.getValue()) {
+                        Object item = getDeserializationContext().mandatoryResolve(node).construct(node);
+                        doConfiguration(item);
                     }
+                } else {
+                    doConfiguration(target);
+                }
+            }
+
+            private void doConfiguration(Object item) {
+                if (item instanceof RouteConfigurationDefinition) {
+                    CamelContextAware.trySetCamelContext(getRouteConfigurationCollection(), getCamelContext());
+                    getRouteConfigurationCollection().routeConfiguration((RouteConfigurationDefinition) item);
                 }
             }
         };
     }
 
-    private static Node preConfigureNode(Node root) {
-        Node target = root;
+    private Object preConfigureNode(Node root) throws Exception {
+        Object target = root;
 
-        // check if the yaml is a camel-k yaml with embedded routes (called flow(s))
+        // check if the yaml is a camel-k yaml with embedded binding/routes (called flow(s))
         if (Objects.equals(root.getNodeType(), NodeType.MAPPING)) {
             final MappingNode mn = YamlDeserializerSupport.asMappingNode(root);
-            boolean camelk = anyTupleMatches(mn.getValue(), "apiVersion", "camel.apache.org/v1") &&
+            // camel-k: integration
+            boolean integration = anyTupleMatches(mn.getValue(), "apiVersion", v -> v.startsWith(INTEGRATION_VERSION)) &&
                     anyTupleMatches(mn.getValue(), "kind", "Integration");
-            if (camelk) {
-                Node routes = nodeAt(root, "/spec/flows");
-                if (routes == null) {
-                    routes = nodeAt(root, "/spec/flow");
-                }
-                if (routes != null) {
-                    target = routes;
-                }
+            // camel-k: kamelet binding are still at v1alpha1
+            boolean binding = anyTupleMatches(mn.getValue(), "apiVersion", v -> v.startsWith(BINDING_VERSION)) &&
+                    anyTupleMatches(mn.getValue(), "kind", "KameletBinding");
+            if (integration) {
+                target = preConfigureIntegration(root, target);
+            } else if (binding) {
+                target = preConfigureKameletBinding(root, target);
             }
         }
 
         return target;
     }
 
-    private static boolean anyTupleMatches(List<NodeTuple> list, String aKey, String aValue) {
-        for (NodeTuple tuple : list) {
-            final String key = asText(tuple.getKeyNode());
-            final Node val = tuple.getValueNode();
-            if (Objects.equals(aKey, key) && NodeType.SCALAR.equals(val.getNodeType())) {
-                String value = asText(tuple.getValueNode());
-                if (Objects.equals(aValue, value)) {
-                    return true;
+    /**
+     * Camel K Integration file
+     */
+    private Object preConfigureIntegration(Node root, Object target) {
+        Node routes = nodeAt(root, "/spec/flows");
+        if (routes == null) {
+            routes = nodeAt(root, "/spec/flow");
+        }
+        if (routes != null) {
+            target = routes;
+        }
+        return target;
+    }
+
+    /**
+     * Camel K Kamelet Binding file
+     */
+    private Object preConfigureKameletBinding(Node root, Object target) throws Exception {
+        final RouteDefinition route = new RouteDefinition();
+        String routeId = asText(nodeAt(root, "/metadata/name"));
+        if (routeId != null) {
+            route.routeId(routeId);
+        }
+
+        // kamelet binding is a bit more complex, so grab the source and sink
+        // and map those to Camel route definitions
+        MappingNode source = asMappingNode(nodeAt(root, "/spec/source"));
+        MappingNode sink = asMappingNode(nodeAt(root, "/spec/sink"));
+        if (source != null && sink != null) {
+            // source at the beginning (mandatory)
+            String from = extractCamelEndpointUri(source);
+            route.from(from);
+
+            // steps in the middle (optional)
+            Node steps = nodeAt(root, "/spec/steps");
+            if (steps != null) {
+                SequenceNode sn = asSequenceNode(steps);
+                for (Node node : sn.getValue()) {
+                    MappingNode step = asMappingNode(node);
+                    String uri = extractCamelEndpointUri(step);
+                    if (uri != null) {
+                        route.to(uri);
+                    }
                 }
             }
+
+            // sink is at the end (mandatory)
+            String to = extractCamelEndpointUri(sink);
+            route.to(to);
+
+            // is there any error handler?
+            // TODO: set it globally via configuration so its inherited
+            MappingNode errorHandler = asMappingNode(nodeAt(root, "/spec/errorHandler"));
+            if (errorHandler != null) {
+                // there are 5 different error handlers, which one is it
+                NodeTuple nt = errorHandler.getValue().get(0);
+                String ehName = asText(nt.getKeyNode());
+                if ("dead-letter-channel".equals(ehName)) {
+                    DeadLetterChannelBuilder dlcb = new DeadLetterChannelBuilder();
+               }
+            }
+
+            target = route;
+        }
+
+        return target;
+    }
+
+    private String extractCamelEndpointUri(MappingNode node) throws Exception {
+        MappingNode mn = null;
+        Node ref = nodeAt(node, "/ref");
+        if (ref != null) {
+            mn = asMappingNode(ref);
+        }
+
+        // extract uri is different if kamelet or not
+        boolean kamelet = mn != null && anyTupleMatches(mn.getValue(), "kind", "Kamelet");
+        boolean strimzi
+                = !kamelet && mn != null && anyTupleMatches(mn.getValue(), "apiVersion", v -> v.startsWith(STRIMZI_VERSION))
+                        && anyTupleMatches(mn.getValue(), "kind", "KafkaTopic");
+        String uri;
+        if (kamelet || strimzi) {
+            uri = extractTupleValue(mn.getValue(), "name");
+        } else {
+            uri = extractTupleValue(node.getValue(), "uri");
+        }
+
+        // properties
+        MappingNode prop = asMappingNode(nodeAt(node, "/properties"));
+        Map<String, Object> params = asMap(prop);
+        if (params != null && !params.isEmpty()) {
+            String query = URISupport.createQueryString(params);
+            uri = uri + "?" + query;
+        }
+
+        if (kamelet) {
+            return "kamelet:" + uri;
+        } else if (strimzi) {
+            return "kafka:" + uri;
+        } else {
+            return uri;
         }
-        return false;
     }
 
 }
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
index ed74dc6..e6821e0 100644
--- 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
@@ -18,8 +18,11 @@ package org.apache.camel.dsl.yaml;
 
 import java.io.FileNotFoundException;
 import java.io.InputStream;
+import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Objects;
+import java.util.function.Predicate;
 
 import org.apache.camel.builder.RouteBuilder;
 import org.apache.camel.dsl.support.RouteBuilderLoaderSupport;
@@ -36,10 +39,14 @@ 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.nodes.NodeTuple;
+import org.snakeyaml.engine.v2.nodes.NodeType;
 import org.snakeyaml.engine.v2.parser.Parser;
 import org.snakeyaml.engine.v2.parser.ParserImpl;
 import org.snakeyaml.engine.v2.scanner.StreamReader;
 
+import static org.apache.camel.dsl.yaml.common.YamlDeserializerSupport.asText;
+
 public abstract class YamlRoutesBuilderLoaderSupport extends RouteBuilderLoaderSupport {
     public static final String DESERIALIZATION_MODE = "CamelYamlDslDeserializationMode";
 
@@ -130,4 +137,34 @@ public abstract class YamlRoutesBuilderLoaderSupport extends RouteBuilderLoaderS
     }
 
     protected abstract RouteBuilder builder(Node node);
+
+    protected boolean anyTupleMatches(List<NodeTuple> list, String aKey, String aValue) {
+        return anyTupleMatches(list, aKey, Predicate.isEqual(aValue));
+    }
+
+    protected boolean anyTupleMatches(List<NodeTuple> list, String aKey, Predicate<String> predicate) {
+        for (NodeTuple tuple : list) {
+            final String key = asText(tuple.getKeyNode());
+            final Node val = tuple.getValueNode();
+            if (Objects.equals(aKey, key) && NodeType.SCALAR.equals(val.getNodeType())) {
+                String value = asText(tuple.getValueNode());
+                if (predicate.test(value)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    protected String extractTupleValue(List<NodeTuple> list, String aKey) {
+        for (NodeTuple tuple : list) {
+            final String key = asText(tuple.getKeyNode());
+            final Node val = tuple.getValueNode();
+            if (Objects.equals(aKey, key) && NodeType.SCALAR.equals(val.getNodeType())) {
+                return asText(tuple.getValueNode());
+            }
+        }
+        return null;
+    }
+
 }
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletBindingLoaderTest.groovy b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletBindingLoaderTest.groovy
new file mode 100644
index 0000000..d80319e
--- /dev/null
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/groovy/org/apache/camel/dsl/yaml/KameletBindingLoaderTest.groovy
@@ -0,0 +1,335 @@
+/*
+ * 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.dsl.yaml.support.YamlTestSupport
+import org.apache.camel.model.ToDefinition
+
+class KameletBindingLoaderTest extends YamlTestSupport {
+    @Override
+    def doSetup() {
+        context.start()
+    }
+
+    def "kamelet binding from kamelet to kamelet"() {
+        when:
+            loadBindings('''
+                apiVersion: camel.apache.org/v1alpha1
+                kind: KameletBinding
+                metadata:
+                  name: timer-event-source                  
+                spec:
+                  source:
+                    ref:
+                      kind: Kamelet
+                      apiVersion: camel.apache.org/v1
+                      name: timer-source
+                    properties:
+                      message: "Hello world!"
+                  sink:
+                    ref:
+                      kind: Kamelet
+                      apiVersion: camel.apache.org/v1
+                      name: log-sink
+            ''')
+        then:
+            context.routeDefinitions.size() == 3
+
+            with (context.routeDefinitions[0]) {
+                routeId == 'timer-event-source'
+                input.endpointUri == 'kamelet:timer-source?message=Hello+world%21'
+                outputs.size() == 1
+                with (outputs[0], ToDefinition) {
+                    endpointUri == 'kamelet:log-sink'
+                }
+            }
+    }
+
+    def "kamelet binding from uri to kamelet"() {
+        when:
+        loadBindings('''
+                apiVersion: camel.apache.org/v1alpha1
+                kind: KameletBinding
+                metadata:
+                  name: timer-event-source                  
+                spec:
+                  source:
+                    uri: timer:foo
+                  sink:
+                    ref:
+                      kind: Kamelet
+                      apiVersion: camel.apache.org/v1
+                      name: log-sink
+            ''')
+        then:
+        context.routeDefinitions.size() == 2
+
+        with (context.routeDefinitions[0]) {
+            routeId == 'timer-event-source'
+            input.endpointUri == 'timer:foo'
+            outputs.size() == 1
+            with (outputs[0], ToDefinition) {
+                endpointUri == 'kamelet:log-sink'
+            }
+        }
+    }
+
+    def "kamelet binding from uri to uri"() {
+        when:
+        loadBindings('''
+                apiVersion: camel.apache.org/v1alpha1
+                kind: KameletBinding
+                metadata:
+                  name: timer-event-source                  
+                spec:
+                  source:
+                    uri: timer:foo
+                  sink:
+                    uri: log:bar
+            ''')
+        then:
+        context.routeDefinitions.size() == 1
+
+        with (context.routeDefinitions[0]) {
+            routeId == 'timer-event-source'
+            input.endpointUri == 'timer:foo'
+            outputs.size() == 1
+            with (outputs[0], ToDefinition) {
+                endpointUri == 'log:bar'
+            }
+        }
+    }
+
+    def "kamelet binding steps"() {
+        when:
+        loadBindings('''
+            apiVersion: camel.apache.org/v1alpha1
+            kind: KameletBinding
+            metadata:
+              name: steps-binding
+            spec:
+              source:
+                ref:
+                  kind: Kamelet
+                  apiVersion: camel.apache.org/v1alpha1
+                  name: timer-source
+                properties:
+                  message: "Camel"
+              steps:
+              - ref:
+                  kind: Kamelet
+                  apiVersion: camel.apache.org/v1alpha1
+                  name: prefix-action
+                properties:
+                  prefix: "Apache"
+              - ref:
+                  kind: Kamelet
+                  apiVersion: camel.apache.org/v1alpha1
+                  name: prefix-action
+                properties:
+                  prefix: "Hello"
+              sink:
+                uri: log:info
+                ''')
+        then:
+        context.routeDefinitions.size() == 4
+
+        with (context.routeDefinitions[0]) {
+            routeId == 'steps-binding'
+            input.endpointUri == 'kamelet:timer-source?message=Camel'
+            outputs.size() == 3
+            with (outputs[2], ToDefinition) {
+                endpointUri == 'log:info'
+            }
+        }
+    }
+
+    def "kamelet binding steps kamelet uri"() {
+        when:
+        loadBindings('''
+            apiVersion: camel.apache.org/v1alpha1
+            kind: KameletBinding
+            metadata:
+              name: steps-binding
+            spec:
+              source:
+                ref:
+                  kind: Kamelet
+                  apiVersion: camel.apache.org/v1alpha1
+                  name: timer-source
+                properties:
+                  message: "Camel"
+              steps:
+              - ref:
+                  kind: Kamelet
+                  apiVersion: camel.apache.org/v1alpha1
+                  name: prefix-action
+                properties:
+                  prefix: "Apache"
+              - uri: mock:dummy
+              sink:
+                uri: log:info
+                ''')
+        then:
+        context.routeDefinitions.size() == 3
+
+        with (context.routeDefinitions[0]) {
+            routeId == 'steps-binding'
+            input.endpointUri == 'kamelet:timer-source?message=Camel'
+            outputs.size() == 3
+            with (outputs[1], ToDefinition) {
+                endpointUri == 'mock:dummy'
+            }
+            with (outputs[2], ToDefinition) {
+                endpointUri == 'log:info'
+            }
+        }
+    }
+
+    def "kamelet binding steps uri uri"() {
+        when:
+        loadBindings('''
+            apiVersion: camel.apache.org/v1alpha1
+            kind: KameletBinding
+            metadata:
+              name: steps-binding
+            spec:
+              source:
+                ref:
+                  kind: Kamelet
+                  apiVersion: camel.apache.org/v1alpha1
+                  name: timer-source
+                properties:
+                  message: "Camel"
+              steps:
+              - uri: mock:dummy
+              - uri: mock:dummy2
+              sink:
+                uri: log:info
+                ''')
+        then:
+        context.routeDefinitions.size() == 2
+
+        with (context.routeDefinitions[0]) {
+            routeId == 'steps-binding'
+            input.endpointUri == 'kamelet:timer-source?message=Camel'
+            outputs.size() == 3
+            with (outputs[0], ToDefinition) {
+                endpointUri == 'mock:dummy'
+            }
+            with (outputs[1], ToDefinition) {
+                endpointUri == 'mock:dummy2'
+            }
+            with (outputs[2], ToDefinition) {
+                endpointUri == 'log:info'
+            }
+        }
+    }
+
+    def "kamelet binding from kamelet to strimzi"() {
+        when:
+
+        // stub kafka for testing as it requires to setup connection to a real kafka broker
+        context.addComponent("kafka", context.getComponent("stub"))
+
+        loadBindings('''
+                apiVersion: camel.apache.org/v1alpha1
+                kind: KameletBinding
+                metadata:
+                  name: timer-event-source                  
+                spec:
+                  source:
+                    ref:
+                      kind: Kamelet
+                      apiVersion: camel.apache.org/v1
+                      name: timer-source
+                    properties:
+                      message: "Hello world!"
+                  sink:
+                    ref:
+                      kind: KafkaTopic
+                      apiVersion: kafka.strimzi.io/v1beta2
+                      name: my-topic
+            ''')
+        then:
+        context.routeDefinitions.size() == 2
+
+        with (context.routeDefinitions[0]) {
+            routeId == 'timer-event-source'
+            input.endpointUri == 'kamelet:timer-source?message=Hello+world%21'
+            outputs.size() == 1
+            with (outputs[0], ToDefinition) {
+                endpointUri == 'kafka:my-topic'
+            }
+        }
+    }
+
+    def "kamelet binding with error handler"() {
+        when:
+
+        // stub kafka for testing as it requires to setup connection to a real kafka broker
+        context.addComponent("kafka", context.getComponent("stub"))
+
+        loadBindings('''
+                apiVersion: camel.apache.org/v1alpha1
+                kind: KameletBinding
+                metadata:
+                  name: timer-event-source                  
+                spec:
+                  source:
+                    ref:
+                      kind: Kamelet
+                      apiVersion: camel.apache.org/v1
+                      name: timer-source
+                    properties:
+                      message: "Hello world!"
+                  sink:
+                    ref:
+                      kind: Kamelet
+                      apiVersion: camel.apache.org/v1alpha1
+                      name: log-sink
+                  errorHandler:
+                    dead-letter-channel:
+                      endpoint:
+                        ref:
+                          kind: Kamelet
+                          apiVersion: camel.apache.org/v1alpha1
+                          name: error-handler
+                        properties:
+                          log-message: "ERROR!"
+                          kafka-brokers: my-broker
+                          kafka-topic: my-first-test
+                          kafka-service-account-id: scott
+                          kafka-service-account-secret: tiger
+                      parameters:
+                        maximumRedeliveries: 1
+                        redeliveryDelay: 2000    
+                    ''')
+        then:
+        context.routeDefinitions.size() == 2
+
+        with (context.routeDefinitions[0]) {
+            routeId == 'timer-event-source'
+            input.endpointUri == 'kamelet:timer-source?message=Hello+world%21'
+            outputs.size() == 1
+            with (outputs[0], ToDefinition) {
+                endpointUri == 'kafka:my-topic'
+            }
+        }
+    }
+
+}
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 efd01cf..e9da693 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
@@ -25,6 +25,7 @@ import org.apache.camel.CamelContext
 import org.apache.camel.FluentProducerTemplate
 import org.apache.camel.builder.RouteBuilder
 import org.apache.camel.component.mock.MockEndpoint
+import org.apache.camel.dsl.yaml.KameletRoutesBuilderLoader
 import org.apache.camel.dsl.yaml.YamlRoutesBuilderLoader
 import org.apache.camel.dsl.yaml.common.YamlDeserializationMode
 import org.apache.camel.impl.DefaultCamelContext
@@ -84,6 +85,17 @@ class YamlTestSupport extends Specification implements HasCamelContext {
         )
     }
 
+    def loadKamelets(Resource... resources) {
+        loadKamelets(resources.toList())
+    }
+
+    def loadKamelets(Collection<Resource> resources) {
+        KameletRoutesBuilderLoader kl = new KameletRoutesBuilderLoader()
+        kl.setCamelContext(context)
+        kl.start()
+        resources.forEach(r -> kl.loadRoutesBuilder(r))
+    }
+
     def loadKamelets(String... resources) {
         int index = 0
 
@@ -94,6 +106,16 @@ class YamlTestSupport extends Specification implements HasCamelContext {
         )
     }
 
+    def loadBindings(String... resources) {
+        int index = 0
+
+        context.routesLoader.loadRoutes(
+            resources.collect {
+                it -> ResourceHelper.fromString("binding-${index++}.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/log-sink.kamelet.yaml b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/log-sink.kamelet.yaml
new file mode 100644
index 0000000..54b37d52
--- /dev/null
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/log-sink.kamelet.yaml
@@ -0,0 +1,61 @@
+# ---------------------------------------------------------------------------
+# 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: log-sink
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "main-SNAPSHOT"
+    camel.apache.org/kamelet.icon: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pg0KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE2LjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPg0KPCFET0NUWVBFIHN2ZyBQVUJMSUMgIi0vL1czQy8vRFREIFNWRyAxLjEvL0VOIiAiaHR0cDovL3d3dy53My5vcmcvR3JhcGhpY3MvU1ZHLzEuMS9EVEQvc3ZnMTEuZHRkIj4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodH [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Logging"
+  labels:
+    camel.apache.org/kamelet.type: "sink"
+spec:
+  definition:
+    title: "Log Sink"
+    description: |-
+      A sink that logs all data that it receives, useful for debugging purposes.
+    type: object
+    properties:
+      showHeaders:
+        title: Show Headers
+        description: Show the headers received
+        type: boolean
+        default: false
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+      showStreams:
+        title: Show Streams
+        description: Show the stream bodies (they may not be available in following steps)
+        type: boolean
+        default: false
+        x-descriptors:
+          - 'urn:alm:descriptor:com.tectonic.ui:checkbox'
+  dependencies:
+    - "camel:kamelet"
+    - "camel:log"
+  flow:
+    from:
+      uri: "kamelet:source"
+      steps:
+        - to:
+            uri: "log:info"
+            parameters:
+              showHeaders: "{{?showHeaders}}"
+              showStreams: "{{?showStreams}}"
\ No newline at end of file
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/prefix-action.kamelet.yaml b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/prefix-action.kamelet.yaml
new file mode 100644
index 0000000..e8e9b6a
--- /dev/null
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/prefix-action.kamelet.yaml
@@ -0,0 +1,45 @@
+# ---------------------------------------------------------------------------
+# 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: prefix-action
+  labels:
+    camel.apache.org/kamelet.type: "step"
+spec:
+  definition:
+    title: "Prefix"
+    description: "Adds a prefix to the incoming payload"
+    required:
+      - prefix
+    properties:
+      prefix:
+        title: Prefix
+        description: The prefix to add
+        type: string
+  types:
+    in:
+      mediaType: text/plain
+    out:
+      mediaType: text/plain
+  flow:
+    from:
+      uri: "kamelet:source"
+      steps:
+        - set-body:
+            simple: "{{prefix}} ${body}"
\ No newline at end of file
diff --git a/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/timer-source.kamelet.yaml b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/timer-source.kamelet.yaml
new file mode 100644
index 0000000..96195c1
--- /dev/null
+++ b/dsl/camel-yaml-dsl/camel-yaml-dsl/src/test/resources/kamelets/timer-source.kamelet.yaml
@@ -0,0 +1,69 @@
+# ---------------------------------------------------------------------------
+# 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: timer-source
+  annotations:
+    camel.apache.org/kamelet.support.level: "Preview"
+    camel.apache.org/catalog.version: "main-SNAPSHOT"
+    camel.apache.org/kamelet.icon: data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gU3ZnIFZlY3RvciBJY29ucyA6IGh0dHA6Ly93d3cub25saW5ld2ViZm9udHMuY29tL2ljb24gLS0+DQo8IURPQ1RZUEUgc3ZnIFBVQkxJQyAiLS8vVzNDLy9EVEQgU1ZHIDEuMS8vRU4iICJodHRwOi8vd3d3LnczLm9yZy9HcmFwaGljcy9TVkcvMS4xL0RURC9zdmcxMS5kdGQiPg0KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm9 [...]
+    camel.apache.org/provider: "Apache Software Foundation"
+    camel.apache.org/kamelet.group: "Timer"
+  labels:
+    camel.apache.org/kamelet.type: source
+    camel.apache.org/kamelet.verified: "true"
+spec:
+  definition:
+    title: Timer Source
+    description: Produces periodic events with a custom payload.
+    required:
+      - message
+    type: object
+    properties:
+      period:
+        title: Period
+        description: The interval between two events in milliseconds
+        type: integer
+        default: 1000
+      message:
+        title: Message
+        description: The message to generate
+        type: string
+        example: hello world
+      contentType:
+        title: Content Type
+        description: The content type of the message being generated
+        type: string
+        default: text/plain
+  dependencies:
+    - "camel:core"
+    - "camel:timer"
+    - "camel:kamelet"
+  flow:
+    from:
+      uri: timer:tick
+      parameters:
+        period: "{{period}}"
+      steps:
+        - set-body:
+            constant: "{{message}}"
+        - set-header:
+            name: "Content-Type"
+            constant: "{{contentType}}"
+        - to: kamelet:sink
\ No newline at end of file