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

[camel] branch main updated: Preliminary support for initializing kamelets from templates (#5865)

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

orpiske 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 fb44eaf  Preliminary support for initializing kamelets from templates (#5865)
fb44eaf is described below

commit fb44eaf03196dd7286703e56faec1dd6addd04a5
Author: Otavio Rodolfo Piske <or...@users.noreply.github.com>
AuthorDate: Tue Jul 27 15:01:56 2021 +0200

    Preliminary support for initializing kamelets from templates (#5865)
---
 .../camel/github/GitHubResourceResolver.java       |   9 +-
 dsl/camel-jbang/camel-jbang-core/pom.xml           |   6 +-
 .../camel/dsl/jbang/core/api/TemplateParser.java   |  26 +++++
 .../jbang/core/commands/AbstractInitKamelet.java   | 100 +++++++++++++++++++
 .../common/exceptions/ResourceAlreadyExists.java   |  28 ++++++
 .../core/templates/VelocityTemplateParser.java     | 107 +++++++++++++++++++++
 .../camel-jbang-main/dist/CamelJBang.java          | 107 ++++++++++++++++++++-
 dsl/camel-jbang/camel-jbang-main/pom.xml           |   9 ++
 .../src/main/jbang/main/CamelJBang.java            | 107 ++++++++++++++++++++-
 9 files changed, 495 insertions(+), 4 deletions(-)

diff --git a/components/camel-resourceresolver-github/src/main/java/org/apache/camel/github/GitHubResourceResolver.java b/components/camel-resourceresolver-github/src/main/java/org/apache/camel/github/GitHubResourceResolver.java
index e694d5d..af6760c 100644
--- a/components/camel-resourceresolver-github/src/main/java/org/apache/camel/github/GitHubResourceResolver.java
+++ b/components/camel-resourceresolver-github/src/main/java/org/apache/camel/github/GitHubResourceResolver.java
@@ -28,8 +28,12 @@ public class GitHubResourceResolver extends ServiceSupport implements org.apache
     // github:apache:camel:aws-ddb-streams-source.kamelet.yaml
     // https://raw.githubusercontent.com/apache/camel-kamelets/main/aws-ddb-streams-source.kamelet.yaml
     private static final String GITHUB_URL = "https://raw.githubusercontent.com/%s/%s/%s/%s";
+    private static final String DEFAULT_BRANCH = "main";
+
     private CamelContext camelContext;
 
+    private String branch = DEFAULT_BRANCH;
+
     @Override
     public CamelContext getCamelContext() {
         return camelContext;
@@ -40,6 +44,10 @@ public class GitHubResourceResolver extends ServiceSupport implements org.apache
         this.camelContext = camelContext;
     }
 
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
     @Override
     public String getSupportedScheme() {
         return "github";
@@ -51,7 +59,6 @@ public class GitHubResourceResolver extends ServiceSupport implements org.apache
         // scheme not in use as its github
         String org = null;
         String rep = null;
-        String branch = "main"; // default branch is main
         String name = null;
 
         if (parts.length == 3) {
diff --git a/dsl/camel-jbang/camel-jbang-core/pom.xml b/dsl/camel-jbang/camel-jbang-core/pom.xml
index 5ff6dac..3b2ce54 100644
--- a/dsl/camel-jbang/camel-jbang-core/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-core/pom.xml
@@ -38,11 +38,15 @@
             <groupId>org.apache.camel</groupId>
             <artifactId>camel-kamelet-main</artifactId>
         </dependency>
-
         <dependency>
             <groupId>commons-io</groupId>
             <artifactId>commons-io</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+            <version>${velocity-version}</version>
+        </dependency>
 
     </dependencies>
 
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/api/TemplateParser.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/api/TemplateParser.java
new file mode 100644
index 0000000..28e79e9
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/api/TemplateParser.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.api;
+
+import java.io.Writer;
+
+import org.apache.camel.CamelException;
+
+public interface TemplateParser {
+    void parse(String templateFileName, Writer writer) throws CamelException;
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AbstractInitKamelet.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AbstractInitKamelet.java
new file mode 100644
index 0000000..fc93aa1
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/AbstractInitKamelet.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.dsl.jbang.core.commands;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.CamelException;
+import org.apache.camel.dsl.jbang.core.common.exceptions.ResourceAlreadyExists;
+import org.apache.camel.github.GitHubResourceResolver;
+import org.apache.camel.main.KameletMain;
+import org.apache.camel.spi.Resource;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class AbstractInitKamelet {
+    private static final Logger LOG = LoggerFactory.getLogger(AbstractInitKamelet.class);
+
+    private String resourceLocation;
+    private String branch;
+
+    protected void setResourceLocation(String baseResourceLocation, String resourcePath) {
+        this.resourceLocation = baseResourceLocation + ":" + resourcePath;
+        LOG.debug("Resource location is: {}", resourceLocation);
+    }
+
+    public void setBranch(String branch) {
+        this.branch = branch;
+    }
+
+    protected File resolveResource(File destinationDirectory) throws IOException, CamelException {
+        KameletMain main = new KameletMain();
+        main.start();
+        CamelContext context = main.getCamelContext();
+
+        try (GitHubResourceResolver resolver = new GitHubResourceResolver()) {
+            resolver.setCamelContext(context);
+            resolver.setBranch(branch);
+
+            Resource resource = resolver.resolve(resourceLocation);
+
+            if (!resource.exists()) {
+                throw new CamelException("The resource does not exist");
+            }
+
+            String fileName = FilenameUtils.getName(resource.getURL().getPath());
+
+            LOG.debug("Destination directory for the downloaded resources: {}", destinationDirectory.getAbsolutePath());
+            LOG.debug("Downloaded resource file name: {}", fileName);
+            File outputFile = new File(destinationDirectory, fileName);
+
+            File parentDir = outputFile.getParentFile();
+            if (!parentDir.exists()) {
+
+                if (!parentDir.mkdirs()) {
+                    LOG.warn("Failed to create the output directory: {}. It may have been created already", parentDir);
+                }
+            }
+
+            if (outputFile.exists()) {
+                throw new ResourceAlreadyExists(outputFile);
+            } else {
+                try (FileOutputStream fo = new FileOutputStream(outputFile)) {
+                    IOUtils.copy(resource.getInputStream(), fo);
+                }
+            }
+
+            return outputFile;
+        }
+    }
+
+    protected void bootstrap(String branch, String baseResourceLocation, String destination)
+            throws IOException, CamelException {
+
+        setBranch(branch);
+        setResourceLocation(baseResourceLocation, "camel-kamelets:templates/init-template.properties");
+
+        resolveResource(new File(destination));
+
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/exceptions/ResourceAlreadyExists.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/exceptions/ResourceAlreadyExists.java
new file mode 100644
index 0000000..a5def19
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/exceptions/ResourceAlreadyExists.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.common.exceptions;
+
+import java.io.File;
+
+import org.apache.camel.CamelException;
+
+public class ResourceAlreadyExists extends CamelException {
+    public ResourceAlreadyExists(File resource) {
+        super("The destination file already exits: " + resource.getAbsolutePath());
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/templates/VelocityTemplateParser.java b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/templates/VelocityTemplateParser.java
new file mode 100644
index 0000000..d6004b9
--- /dev/null
+++ b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/templates/VelocityTemplateParser.java
@@ -0,0 +1,107 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.camel.dsl.jbang.core.templates;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Properties;
+
+import org.apache.camel.CamelException;
+import org.apache.camel.dsl.jbang.core.api.TemplateParser;
+import org.apache.camel.util.ObjectHelper;
+import org.apache.velocity.Template;
+import org.apache.velocity.VelocityContext;
+import org.apache.velocity.app.Velocity;
+import org.apache.velocity.exception.MethodInvocationException;
+import org.apache.velocity.exception.ParseErrorException;
+import org.apache.velocity.exception.ResourceNotFoundException;
+import org.apache.velocity.runtime.RuntimeConstants;
+
+public class VelocityTemplateParser implements TemplateParser {
+    private final File propertiesFile;
+
+    public VelocityTemplateParser(File templateDir, String propertiesFile) {
+        this(templateDir, new File(propertiesFile));
+    }
+
+    public VelocityTemplateParser(File templateDir, File propertiesFile) {
+        this.propertiesFile = propertiesFile;
+        initializeTemplateEngine(templateDir);
+    }
+
+    private void initializeTemplateEngine(File templateDir) {
+        Properties props = new Properties();
+
+        props.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
+
+        props.put("resource.loader.file.path", templateDir.getAbsolutePath());
+
+        Velocity.init(props);
+    }
+
+    private void overridePropertyList(VelocityContext context, Properties properties, String requiredKameletProperties) {
+        String requiredPropertyList = properties.getProperty(requiredKameletProperties);
+
+        if (ObjectHelper.isNotEmpty(requiredPropertyList)) {
+            context.put(requiredKameletProperties, requiredPropertyList.split(","));
+        }
+    }
+
+    @Override
+    public void parse(String templateFileName, Writer writer) throws CamelException {
+        VelocityContext context = new VelocityContext();
+
+        try {
+            loadTemplateProperties(context);
+        } catch (IOException e) {
+            throw new CamelException("Unable to load the template properties", e);
+        }
+
+        try {
+            Template template = Velocity.getTemplate(templateFileName);
+
+            template.merge(context, writer);
+        } catch (ResourceNotFoundException rnfe) {
+            throw new CamelException("Could not find the template to parse", rnfe);
+        } catch (ParseErrorException pee) {
+            throw new CamelException("Failed parsing the template", pee);
+        } catch (MethodInvocationException mie) {
+            throw new CamelException("Method call within the templated has failed", mie);
+        } catch (Exception e) {
+            throw new CamelException("Unspecified error while loading, parsing or processing the template", e);
+        }
+    }
+
+    private void loadTemplateProperties(VelocityContext context) throws IOException {
+        Properties properties = new Properties();
+
+        try (FileReader propertiesReader = new FileReader(propertiesFile)) {
+            properties.load(propertiesReader);
+        }
+
+        properties.forEach((k, v) -> context.put(k.toString(), v));
+
+        overridePropertyList(context, properties, "kameletProperties");
+        overridePropertyList(context, properties, "requiredKameletProperties");
+        overridePropertyList(context, properties, "kameletBeans");
+        overridePropertyList(context, properties, "fromParameters");
+        overridePropertyList(context, properties, "toParameters");
+    }
+}
diff --git a/dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java b/dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java
index 6c6eb3e..1119563 100755
--- a/dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java
+++ b/dsl/camel-jbang/camel-jbang-main/dist/CamelJBang.java
@@ -30,19 +30,24 @@
 //DEPS org.apache.logging.log4j:log4j-api:2.13.3
 //DEPS org.apache.logging.log4j:log4j-core:2.13.3
 //DEPS org.apache.logging.log4j:log4j-slf4j-impl:2.13.3
+//DEPS org.apache.velocity:velocity-engine-core:2.3
 //DEPS info.picocli:picocli:4.5.0
 
 package main;
 
 import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.util.concurrent.Callable;
 import java.util.regex.Pattern;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelException;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dsl.jbang.core.commands.AbstractInitKamelet;
 import org.apache.camel.dsl.jbang.core.commands.AbstractSearch;
 import org.apache.camel.dsl.jbang.core.common.MatchExtractor;
+import org.apache.camel.dsl.jbang.core.common.exceptions.ResourceAlreadyExists;
 import org.apache.camel.dsl.jbang.core.components.ComponentConverter;
 import org.apache.camel.dsl.jbang.core.components.ComponentDescriptionMatching;
 import org.apache.camel.dsl.jbang.core.components.ComponentPrinter;
@@ -55,6 +60,7 @@ import org.apache.camel.dsl.jbang.core.languages.LanguagePrinter;
 import org.apache.camel.dsl.jbang.core.others.OtherConverter;
 import org.apache.camel.dsl.jbang.core.others.OtherDescriptionMatching;
 import org.apache.camel.dsl.jbang.core.others.OtherPrinter;
+import org.apache.camel.dsl.jbang.core.templates.VelocityTemplateParser;
 import org.apache.camel.dsl.jbang.core.types.Component;
 import org.apache.camel.dsl.jbang.core.types.Kamelet;
 import org.apache.camel.dsl.jbang.core.types.Language;
@@ -410,6 +416,103 @@ class SearchOthers extends AbstractSearch implements Callable<Integer> {
     }
 }
 
+@Command(name = "init", description = "Provide init templates for kamelets and bindings")
+class Init implements Callable<Integer> {
+    @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display the help and sub-commands")
+    private boolean helpRequested = false;
+
+    @Override
+    public Integer call() throws Exception {
+        new CommandLine(this).execute("--help");
+
+        return 0;
+    }
+}
+
+@Command(name = "kamelet", description = "Provide init templates for kamelets")
+class InitKamelet extends AbstractInitKamelet implements Callable<Integer> {
+    @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display the help and sub-commands")
+    private boolean helpRequested = false;
+
+    @CommandLine.ArgGroup(exclusive = true, multiplicity = "1")
+    private ProcessOptions processOptions;
+
+    static class ProcessOptions {
+        @Option(names = { "--bootstrap" },
+                description = "Bootstrap the Kamelet template generator - download the properties file for editing")
+        private boolean bootstrap = false;
+
+        @Option(names = { "--properties-path" }, defaultValue = "", description = "Kamelet name")
+        private String propertiesPath;
+    }
+
+    @Option(names = { "--base-resource-location" }, defaultValue = "github:apache", hidden = true,
+            description = "Where to download the resources from (used for development/testing)")
+    private String baseResourceLocation;
+
+    @Option(names = { "--branch" }, defaultValue = "main", hidden = true,
+            description = "The branch to use when downloading resources from (used for development/testing)")
+    private String branch;
+
+    @Option(names = { "--destination" }, defaultValue = "work",
+            description = "The destination directory where to download the files")
+    private String destination;
+
+    @Override
+    public Integer call() throws Exception {
+        if (processOptions.bootstrap) {
+            bootstrap();
+        } else {
+            generateTemplate();
+        }
+
+        return 0;
+    }
+
+    private int generateTemplate() throws IOException, CamelException {
+        setBranch(branch);
+        setResourceLocation(baseResourceLocation, "camel-kamelets:templates/init-template.kamelet.yaml.vm");
+
+        File workDirectory = new File(destination);
+
+        File localTemplateFile;
+        try {
+            localTemplateFile = resolveResource(workDirectory);
+        } catch (ResourceAlreadyExists e) {
+            System.err.println(e.getMessage());
+            return 1;
+        }
+
+        localTemplateFile.deleteOnExit();
+
+        VelocityTemplateParser templateParser = new VelocityTemplateParser(
+                localTemplateFile.getParentFile(),
+                processOptions.propertiesPath);
+
+        String outputFileName = localTemplateFile.getName().replace(".vm", "");
+        File outputFile = new File(localTemplateFile.getParentFile(), outputFileName);
+
+        try (FileWriter fw = new FileWriter(outputFile)) {
+            templateParser.parse(localTemplateFile.getName(), fw);
+            System.out.println("Template file was written to " + outputFile);
+        }
+
+        return 0;
+    }
+
+    private int bootstrap() throws IOException, CamelException {
+        try {
+            super.bootstrap(branch, baseResourceLocation, destination);
+            return 0;
+        } catch (ResourceAlreadyExists e) {
+            System.err.println(e.getMessage());
+
+            return 1;
+        }
+    }
+
+}
+
 @Command(name = "CamelJBang", mixinStandardHelpOptions = true, version = "CamelJBang 3.12.0-SNAPSHOT",
          description = "A JBang-based Camel app for running Kamelets")
 class CamelJBang implements Callable<Integer> {
@@ -426,7 +529,9 @@ class CamelJBang implements Callable<Integer> {
                         .addSubcommand("kamelets", new SearchKamelets())
                         .addSubcommand("components", new SearchComponents())
                         .addSubcommand("languages", new SearchLanguages())
-                        .addSubcommand("others", new SearchOthers()));
+                        .addSubcommand("others", new SearchOthers()))
+                .addSubcommand("init", new CommandLine(new Init())
+                        .addSubcommand("kamelet", new InitKamelet()));
 
         int exitCode = commandLine.execute(args);
         System.exit(exitCode);
diff --git a/dsl/camel-jbang/camel-jbang-main/pom.xml b/dsl/camel-jbang/camel-jbang-main/pom.xml
index 696fe50..aadcaf6 100644
--- a/dsl/camel-jbang/camel-jbang-main/pom.xml
+++ b/dsl/camel-jbang/camel-jbang-main/pom.xml
@@ -89,6 +89,12 @@
             <groupId>info.picocli</groupId>
             <artifactId>picocli</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.apache.velocity</groupId>
+            <artifactId>velocity-engine-core</artifactId>
+            <version>${velocity-version}</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -127,6 +133,9 @@
                                 <replace token="${camel.jbang.picocli.version}" value="${picocli-version}" dir="${dist.dir}">
                                     <include name="**/*.java"/>
                                 </replace>
+                                <replace token="${camel.jbang.velocity.version}" value="${velocity-version}" dir="${dist.dir}">
+                                    <include name="**/*.java"/>
+                                </replace>
                                 <chmod file="${dist.dir}/*.java" perm="u+x" />
                             </tasks>
                         </configuration>
diff --git a/dsl/camel-jbang/camel-jbang-main/src/main/jbang/main/CamelJBang.java b/dsl/camel-jbang/camel-jbang-main/src/main/jbang/main/CamelJBang.java
index 7c067bd..8da4635 100755
--- a/dsl/camel-jbang/camel-jbang-main/src/main/jbang/main/CamelJBang.java
+++ b/dsl/camel-jbang/camel-jbang-main/src/main/jbang/main/CamelJBang.java
@@ -30,19 +30,24 @@
 //DEPS org.apache.logging.log4j:log4j-api:${camel.jbang.log4j2.version}
 //DEPS org.apache.logging.log4j:log4j-core:${camel.jbang.log4j2.version}
 //DEPS org.apache.logging.log4j:log4j-slf4j-impl:${camel.jbang.log4j2.version}
+//DEPS org.apache.velocity:velocity-engine-core:${camel.jbang.velocity.version}
 //DEPS info.picocli:picocli:${camel.jbang.picocli.version}
 
 package main;
 
 import java.io.File;
+import java.io.FileWriter;
 import java.io.IOException;
 import java.util.concurrent.Callable;
 import java.util.regex.Pattern;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.CamelException;
 import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.dsl.jbang.core.commands.AbstractInitKamelet;
 import org.apache.camel.dsl.jbang.core.commands.AbstractSearch;
 import org.apache.camel.dsl.jbang.core.common.MatchExtractor;
+import org.apache.camel.dsl.jbang.core.common.exceptions.ResourceAlreadyExists;
 import org.apache.camel.dsl.jbang.core.components.ComponentConverter;
 import org.apache.camel.dsl.jbang.core.components.ComponentDescriptionMatching;
 import org.apache.camel.dsl.jbang.core.components.ComponentPrinter;
@@ -55,6 +60,7 @@ import org.apache.camel.dsl.jbang.core.languages.LanguagePrinter;
 import org.apache.camel.dsl.jbang.core.others.OtherConverter;
 import org.apache.camel.dsl.jbang.core.others.OtherDescriptionMatching;
 import org.apache.camel.dsl.jbang.core.others.OtherPrinter;
+import org.apache.camel.dsl.jbang.core.templates.VelocityTemplateParser;
 import org.apache.camel.dsl.jbang.core.types.Component;
 import org.apache.camel.dsl.jbang.core.types.Kamelet;
 import org.apache.camel.dsl.jbang.core.types.Language;
@@ -410,6 +416,103 @@ class SearchOthers extends AbstractSearch implements Callable<Integer> {
     }
 }
 
+@Command(name = "init", description = "Provide init templates for kamelets and bindings")
+class Init implements Callable<Integer> {
+    @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display the help and sub-commands")
+    private boolean helpRequested = false;
+
+    @Override
+    public Integer call() throws Exception {
+        new CommandLine(this).execute("--help");
+
+        return 0;
+    }
+}
+
+@Command(name = "kamelet", description = "Provide init templates for kamelets")
+class InitKamelet extends AbstractInitKamelet implements Callable<Integer> {
+    @Option(names = { "-h", "--help" }, usageHelp = true, description = "Display the help and sub-commands")
+    private boolean helpRequested = false;
+
+    @CommandLine.ArgGroup(exclusive = true, multiplicity = "1")
+    private ProcessOptions processOptions;
+
+    static class ProcessOptions {
+        @Option(names = { "--bootstrap" },
+                description = "Bootstrap the Kamelet template generator - download the properties file for editing")
+        private boolean bootstrap = false;
+
+        @Option(names = { "--properties-path" }, defaultValue = "", description = "Kamelet name")
+        private String propertiesPath;
+    }
+
+    @Option(names = { "--base-resource-location" }, defaultValue = "github:apache", hidden = true,
+            description = "Where to download the resources from (used for development/testing)")
+    private String baseResourceLocation;
+
+    @Option(names = { "--branch" }, defaultValue = "main", hidden = true,
+            description = "The branch to use when downloading resources from (used for development/testing)")
+    private String branch;
+
+    @Option(names = { "--destination" }, defaultValue = "work",
+            description = "The destination directory where to download the files")
+    private String destination;
+
+    @Override
+    public Integer call() throws Exception {
+        if (processOptions.bootstrap) {
+            bootstrap();
+        } else {
+            generateTemplate();
+        }
+
+        return 0;
+    }
+
+    private int generateTemplate() throws IOException, CamelException {
+        setBranch(branch);
+        setResourceLocation(baseResourceLocation, "camel-kamelets:templates/init-template.kamelet.yaml.vm");
+
+        File workDirectory = new File(destination);
+
+        File localTemplateFile;
+        try {
+            localTemplateFile = resolveResource(workDirectory);
+        } catch (ResourceAlreadyExists e) {
+            System.err.println(e.getMessage());
+            return 1;
+        }
+
+        localTemplateFile.deleteOnExit();
+
+        VelocityTemplateParser templateParser = new VelocityTemplateParser(
+                localTemplateFile.getParentFile(),
+                processOptions.propertiesPath);
+
+        String outputFileName = localTemplateFile.getName().replace(".vm", "");
+        File outputFile = new File(localTemplateFile.getParentFile(), outputFileName);
+
+        try (FileWriter fw = new FileWriter(outputFile)) {
+            templateParser.parse(localTemplateFile.getName(), fw);
+            System.out.println("Template file was written to " + outputFile);
+        }
+
+        return 0;
+    }
+
+    private int bootstrap() throws IOException, CamelException {
+        try {
+            super.bootstrap(branch, baseResourceLocation, destination);
+            return 0;
+        } catch (ResourceAlreadyExists e) {
+            System.err.println(e.getMessage());
+
+            return 1;
+        }
+    }
+
+}
+
 @Command(name = "CamelJBang", mixinStandardHelpOptions = true, version = "CamelJBang ${camel.jbang.version}",
          description = "A JBang-based Camel app for running Kamelets")
 class CamelJBang implements Callable<Integer> {
@@ -426,7 +529,9 @@ class CamelJBang implements Callable<Integer> {
                         .addSubcommand("kamelets", new SearchKamelets())
                         .addSubcommand("components", new SearchComponents())
                         .addSubcommand("languages", new SearchLanguages())
-                        .addSubcommand("others", new SearchOthers()));
+                        .addSubcommand("others", new SearchOthers()))
+                .addSubcommand("init", new CommandLine(new Init())
+                        .addSubcommand("kamelet", new InitKamelet()));
 
         int exitCode = commandLine.execute(args);
         System.exit(exitCode);