This is an automated email from the ASF dual-hosted git repository. mgubaidullin pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
commit 3b7cfae5d515638ffda930ee238ae725b45b1a4c Author: Marat Gubaidullin <[email protected]> AuthorDate: Tue Aug 25 11:09:38 2026 -0400 Karavan-app DevMode Redesign --- karavan-devmode/.dockerignore | 5 ++ karavan-devmode/Dockerfile | 53 ++++++------ karavan-devmode/entrypoint.sh | 60 ++++++++++++++ karavan-devmode/pom.xml | 65 +++++++++++++++ karavan-devmode/src/main/java/KaravanDevMode.java | 96 ++++++++++++++++++++++ .../src/main/resources/log4j2.component.properties | 2 + .../src/main/resources/log4j2.properties | 21 +++++ 7 files changed, 278 insertions(+), 24 deletions(-) diff --git a/karavan-devmode/.dockerignore b/karavan-devmode/.dockerignore new file mode 100644 index 00000000..8a8b6548 --- /dev/null +++ b/karavan-devmode/.dockerignore @@ -0,0 +1,5 @@ +* +!target/karavan-devmode* +!target/lib/* +!entrypoint.sh +src/* \ No newline at end of file diff --git a/karavan-devmode/Dockerfile b/karavan-devmode/Dockerfile index 1e3e0fc8..b9625ead 100644 --- a/karavan-devmode/Dockerfile +++ b/karavan-devmode/Dockerfile @@ -12,47 +12,52 @@ # 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. - -FROM maven:3.9.12-eclipse-temurin-21 +FROM registry.access.redhat.com/ubi9/openjdk-21:1.24-2.1782292637 LABEL "org.opencontainers.image.title"="karavan-devmode" LABEL "org.opencontainers.image.description"="Apache Camel Karavan DevMode" LABEL "org.opencontainers.image.url"="https://camel.apache.org" LABEL "org.opencontainers.image.licenses"="Apache 2.0" -LABEL "org.opencontainers.image.version"="4.18.1" +LABEL "org.opencontainers.image.version"="4.22.0" + +ENV MAVEN_HOME=/opt/maven +ENV PATH=$MAVEN_HOME/bin:$PATH -RUN apt-get update \ - && apt-get install -y --no-install-recommends git \ - && rm -rf /var/lib/apt/lists/* +USER root +RUN microdnf update -y \ + && microdnf install -y git tar gzip jq \ + && microdnf clean all -ENV JBANG_VERSION=0.137.0 -ENV CAMEL_VERSION=4.18.1 -ENV JBANG_JAVA_OPTIONS="-Dcamel.jbang.version=4.18.1 -Dcamel.jbang.kameletsVersion=4.18.1" ENV KARAVAN="/karavan" -ENV JBANG_DIR="$KARAVAN/.jbang" +ENV KARAVAN_LIB="$KARAVAN/lib" +ENV KARAVAN_SCRIPTS="$KARAVAN/scripts" +ENV DOCKER_CONFIG_DIR="$KARAVAN/.docker" ENV MAVEN_CONFIG="$KARAVAN/.m2" -ENV JBANG_REPO=$MAVEN_CONFIG +ENV MAVEN_REPO="$MAVEN_CONFIG/repository" ENV BUILDER_PATH="$KARAVAN/builder" ENV CODE_DIR="$KARAVAN/code" ENV KAMELETS_DIR="$CODE_DIR/kamelets" -ENV PATH="$PATH:$KARAVAN/.jbang/bin" -RUN mkdir -p $JBANG_REPO \ +ENV RUN_IN_BUILD_MODE=false +ENV RUN_IN_COMPILE_MODE=false + +RUN mkdir -p $MAVEN_REPO \ && mkdir -p $CODE_DIR \ + && mkdir -p $DOCKER_CONFIG_DIR \ && mkdir -p $BUILDER_PATH -RUN useradd -rm -d /karavan -s /bin/bash -g root -G sudo -u 1001 karavan -RUN chown -R karavan $KARAVAN - -RUN chgrp -R 0 $KARAVAN && chmod -R g+rwX $KARAVAN +COPY target/lib/*.jar $KARAVAN_LIB/ +COPY target/karavan-devmode-*.jar $KARAVAN_LIB +COPY entrypoint.sh $KARAVAN/ +RUN chmod +x $KARAVAN/entrypoint.sh +RUN groupadd -g 1001 karavan \ + && useradd -d $KARAVAN -s /bin/bash -g karavan -u 1001 karavan +RUN chown -R karavan:karavan $KARAVAN +RUN chown -R karavan:karavan /home/default USER karavan -RUN curl -Ls https://sh.jbang.dev | bash -s - app setup \ - && jbang trust add --quiet https://github.com/apache/camel \ - && jbang app install camel@apache/camel \ - && jbang config set cache-evict never - EXPOSE 8080 -WORKDIR $CODE_DIR -CMD ["jbang", "-Dcamel.jbang.version=4.18.1", "-Dcamel.jbang.kameletsVersion=4.18.1", "camel@apache/camel", "run", "--source-dir=/karavan/code", "--console"] \ No newline at end of file +WORKDIR $CODE_DIR + +ENTRYPOINT ["/karavan/entrypoint.sh"] \ No newline at end of file diff --git a/karavan-devmode/entrypoint.sh b/karavan-devmode/entrypoint.sh new file mode 100755 index 00000000..685e02fb --- /dev/null +++ b/karavan-devmode/entrypoint.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +# Entrypoint script for running Karavan in different modes + +# Function to print visually separated log headers +log_step() { + echo "" + echo "========================================================================" + echo "$1" + echo "========================================================================" +} +export -f log_step + +# Function to determine KARAVAN_HOST based on the environment (K8s vs Docker) +determine_host() { + log_step "π Determining Platform Host Environment" + local k8s_namespace_file="/var/run/secrets/kubernetes.io/serviceaccount/namespace" + + if [[ -f "$k8s_namespace_file" ]]; then + export NAMESPACE=$(cat "$k8s_namespace_file") + export KARAVAN_HOST="karavan.$NAMESPACE" + echo "β Kubernetes environment detected. KARAVAN_HOST set to: $KARAVAN_HOST" + else + export KARAVAN_HOST="karavan:8080" + echo "π³ Docker environment detected. KARAVAN_HOST set to: $KARAVAN_HOST" + fi +} +export -f determine_host + +# Function to set git credentials +git_auth_username_password() { + log_step "π Configure Git Credentials" + git config --global credential.helper 'cache --timeout=3600' + git_credential_fill() { + echo url=$GIT_REPOSITORY + echo username=$GIT_USERNAME + echo password=$GIT_PASSWORD + } + git_credential_fill | git credential approve +} +export -f git_auth_username_password + +determine_host + +if [[ "$RUN_IN_COMPILE_MODE" == "true" ]]; then + log_step "π·Running compiled..." + java $JAVA_OPTS -cp "$KARAVAN_LIB/*" KaravanDevMode export --local-kamelet-dir="$KAMELETS_DIR" --dir=.export + mvn compile dependency:copy-dependencies -f .export -DoutputDirectory=target/lib + export BASE_PACKAGE_SCAN=$(grep -oP '^camel\.main\.basePackageScan=\K.*' .export/src/main/resources/application.properties) + java $JAVA_OPTS -cp ".export/target/lib/*:.export/target/classes" "$BASE_PACKAGE_SCAN".CamelApplication +elif [[ "$RUN_IN_BUILD_MODE" == "true" ]]; then + log_step "π¦ Packaging..." + secure_ssh_keys + log_step "ποΈ Preparing build.sh script" + java $JAVA_OPTS -cp "$KARAVAN_LIB/*" KaravanDevMode fetchBuildScriptFromPlatform + /karavan/builder/build.sh +else + log_step "π Karavan Developer Mode for $PROJECT_ID" + java $JAVA_OPTS -cp "$KARAVAN_LIB/*" KaravanDevMode run --source-dir="$CODE_DIR" --logging-color=true --console $VERBOSE_OPTION +fi \ No newline at end of file diff --git a/karavan-devmode/pom.xml b/karavan-devmode/pom.xml new file mode 100644 index 00000000..10f3db84 --- /dev/null +++ b/karavan-devmode/pom.xml @@ -0,0 +1,65 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + + <modelVersion>4.0.0</modelVersion> + <groupId>org.apache.camel.karavan</groupId> + <artifactId>karavan-devmode</artifactId> + <version>4.22.0</version> + + <properties> + <compiler-plugin.version>3.14.0</compiler-plugin.version> + <maven.compiler.parameters>true</maven.compiler.parameters> + <maven.compiler.source>21</maven.compiler.source> + <jandex-maven-plugin-version>3.1.1</jandex-maven-plugin-version> + <maven.compiler.target>21</maven.compiler.target> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <camel.version>4.22.0</camel.version> + </properties> + + <dependencyManagement> + <dependencies> + <!-- Camel BOM --> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-bom</artifactId> + <version>${camel.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.apache.camel</groupId> + <artifactId>camel-jbang-core</artifactId> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + </plugin> + <plugin> + <artifactId>maven-dependency-plugin</artifactId> + <executions> + <execution> + <phase>prepare-package</phase> + <goals> + <goal>copy-dependencies</goal> + </goals> + <configuration> + <outputDirectory>${project.build.directory}/lib</outputDirectory> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> diff --git a/karavan-devmode/src/main/java/KaravanDevMode.java b/karavan-devmode/src/main/java/KaravanDevMode.java new file mode 100644 index 00000000..b3bbcfaf --- /dev/null +++ b/karavan-devmode/src/main/java/KaravanDevMode.java @@ -0,0 +1,96 @@ +import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.Duration; +import java.util.Objects; + +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class KaravanDevMode { + + private static final Logger LOGGER = LoggerFactory.getLogger(KaravanDevMode.class.getName()); + + public static void main(String... args) throws Exception { + var projectId = System.getenv("PROJECT_ID"); + var isBuildMode = Objects.equals(System.getenv("RUN_IN_BUILD_MODE"), "true"); + + if (isBuildMode && args.length == 1 && args[0].equals("fetchBuildScriptFromPlatform")) { + Path targetDir = Path.of(System.getenv("BUILDER_PATH")); + fetchFilesFromPlatform("configuration", "build.sh", targetDir); + } else { + if (!isBuildMode) { + Path targetDir = Path.of(System.getenv("CODE_DIR")); + fetchFilesFromPlatform(projectId, null, targetDir); + } + // Start Camel JBang + CamelJBangMain.run(args); + } + } + + private static void fetchFilesFromPlatform(String projectId, String filename, Path targetDir) { + String platformHost = System.getenv("KARAVAN_HOST"); + String sessionId = System.getenv("BUILDER_SESSION_ID"); + + String url = "http://" + platformHost + "/platform/internal/sources/" + projectId + (filename != null ? "/" + filename : ""); + LOGGER.info("Fetching project files from Platform..."); + + try { + HttpClient client = HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build(); + HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().uri(URI.create(url)).GET(); + requestBuilder.header("Cookie", "sessionId=" + sessionId); + HttpResponse<InputStream> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofInputStream()); + if (response.statusCode() == 200) { + unzipStream(response.body(), targetDir); + LOGGER.info("Files successfully downloaded and extracted."); + } else { + LOGGER.info("Failed to fetch project files. Platform returned HTTP " + response.statusCode()); + } + } catch (Exception e) { + LOGGER.error("Error establishing connection to Platform: " + e.getMessage()); + } + } + + private static void unzipStream(InputStream inputStream, Path targetDir) throws Exception { + try (ZipInputStream zis = new ZipInputStream(inputStream)) { + ZipEntry entry; + while ((entry = zis.getNextEntry()) != null) { + Path resolvedPath = targetDir.resolve(entry.getName()).normalize(); + + // Zip Slip vulnerability prevention + if (!resolvedPath.startsWith(targetDir)) { + throw new RuntimeException("Invalid ZIP entry path: " + entry.getName()); + } + + if (entry.isDirectory()) { + Files.createDirectories(resolvedPath); + } else { + Files.createDirectories(resolvedPath.getParent()); + try (OutputStream os = Files.newOutputStream(resolvedPath)) { + zis.transferTo(os); + } + + // Mark file as executable if it is a shell script + if (entry.getName().endsWith(".sh")) { + boolean success = resolvedPath.toFile().setExecutable(true, false); // true = executable, false = for all users (not just owner) + if (!success) { + LOGGER.warn("Failed to set executable permission on: {}", resolvedPath.toAbsolutePath()); + } + } + + LOGGER.info("Extracted file: {}", resolvedPath.toAbsolutePath()); + } + zis.closeEntry(); + } + } + } +} \ No newline at end of file diff --git a/karavan-devmode/src/main/resources/log4j2.component.properties b/karavan-devmode/src/main/resources/log4j2.component.properties new file mode 100644 index 00000000..7fd3fcaf --- /dev/null +++ b/karavan-devmode/src/main/resources/log4j2.component.properties @@ -0,0 +1,2 @@ +# camel-main handled JVM shutdown so disable log4j hook +log4j.shutdownHookEnabled = false \ No newline at end of file diff --git a/karavan-devmode/src/main/resources/log4j2.properties b/karavan-devmode/src/main/resources/log4j2.properties new file mode 100644 index 00000000..8338ca9c --- /dev/null +++ b/karavan-devmode/src/main/resources/log4j2.properties @@ -0,0 +1,21 @@ +# console logger +appender.stdout.type = Console +appender.stdout.name = out +appender.stdout.layout.type = PatternLayout +# logging style that is similar to spring boot +appender.stdout.layout.pattern = %style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{Dim} %highlight{%5p} %style{%pid}{Magenta} %style{---}{Dim} %style{[%15.15t]}{Dim} %style{%-40.40c}{Cyan} %style{:}{Dim} %m%n + +# file logger +appender.file.type = File +appender.file.name = file +appender.file.fileName = ${sys:user.home}${sys:file.separator}.camel${sys:file.separator}${sys:pid}.log +appender.file.createOnDemand = true +appender.file.append = false +appender.file.layout.type = PatternLayout +# logging style that is similar to spring boot +appender.file.layout.pattern = %style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{Dim} %highlight{%5p} %style{%pid}{Magenta} %style{---}{Dim} %style{[%15.15t]}{Dim} %style{%-40.40c}{Cyan} %style{:}{Dim} %m%n + +# log to console and file +rootLogger = INFO,out,file +rootLogger.level = INFO +
