diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..6313b56
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+* text=auto eol=lf
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..008434d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.4.3
+
+
+
+ com.cameleer
+ cameleer-deploy-demo
+ 0.1.0-SNAPSHOT
+ Cameleer Deploy Demo
+ Demo: upload Camel JARs, build containers with agent injection, deploy to K8s
+
+
+ 21
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
diff --git a/src/main/java/com/cameleer/deploy/DeployDemoApplication.java b/src/main/java/com/cameleer/deploy/DeployDemoApplication.java
new file mode 100644
index 0000000..d6e6871
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/DeployDemoApplication.java
@@ -0,0 +1,11 @@
+package com.cameleer.deploy;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class DeployDemoApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(DeployDemoApplication.class, args);
+ }
+}
diff --git a/src/main/java/com/cameleer/deploy/config/DeployProperties.java b/src/main/java/com/cameleer/deploy/config/DeployProperties.java
new file mode 100644
index 0000000..34e9e94
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/config/DeployProperties.java
@@ -0,0 +1,33 @@
+package com.cameleer.deploy.config;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConfigurationProperties(prefix = "cameleer.deploy")
+public class DeployProperties {
+ private String serverUrl = "http://cameleer3-server.cameleer.svc:8081";
+ private String bootstrapToken = "changeme";
+ private String registry = "gitea.siegeln.net/cameleer/demo-apps";
+ private String agentMavenUrl;
+ private String demoNamespace = "cameleer-demo";
+ private String cameleerServerUi = "http://localhost:8081";
+
+ public String getServerUrl() { return serverUrl; }
+ public void setServerUrl(String serverUrl) { this.serverUrl = serverUrl; }
+
+ public String getBootstrapToken() { return bootstrapToken; }
+ public void setBootstrapToken(String bootstrapToken) { this.bootstrapToken = bootstrapToken; }
+
+ public String getRegistry() { return registry; }
+ public void setRegistry(String registry) { this.registry = registry; }
+
+ public String getAgentMavenUrl() { return agentMavenUrl; }
+ public void setAgentMavenUrl(String agentMavenUrl) { this.agentMavenUrl = agentMavenUrl; }
+
+ public String getDemoNamespace() { return demoNamespace; }
+ public void setDemoNamespace(String demoNamespace) { this.demoNamespace = demoNamespace; }
+
+ public String getCameleerServerUi() { return cameleerServerUi; }
+ public void setCameleerServerUi(String cameleerServerUi) { this.cameleerServerUi = cameleerServerUi; }
+}
diff --git a/src/main/java/com/cameleer/deploy/config/WebConfig.java b/src/main/java/com/cameleer/deploy/config/WebConfig.java
new file mode 100644
index 0000000..f89cbc8
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/config/WebConfig.java
@@ -0,0 +1,16 @@
+package com.cameleer.deploy.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.CorsRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class WebConfig implements WebMvcConfigurer {
+ @Override
+ public void addCorsMappings(CorsRegistry registry) {
+ registry.addMapping("/api/**")
+ .allowedOriginPatterns("*")
+ .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
+ .allowedHeaders("*");
+ }
+}
diff --git a/src/main/java/com/cameleer/deploy/controller/DeployController.java b/src/main/java/com/cameleer/deploy/controller/DeployController.java
new file mode 100644
index 0000000..eae6413
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/controller/DeployController.java
@@ -0,0 +1,94 @@
+package com.cameleer.deploy.controller;
+
+import com.cameleer.deploy.config.DeployProperties;
+import com.cameleer.deploy.model.DeployRequest;
+import com.cameleer.deploy.model.DeployedApp;
+import com.cameleer.deploy.service.DeployService;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/apps")
+public class DeployController {
+
+ private final DeployService deployService;
+ private final DeployProperties props;
+ private final ObjectMapper objectMapper;
+
+ public DeployController(DeployService deployService, DeployProperties props, ObjectMapper objectMapper) {
+ this.deployService = deployService;
+ this.props = props;
+ this.objectMapper = objectMapper;
+ }
+
+ @GetMapping
+ public List list() {
+ return deployService.listApps();
+ }
+
+ @GetMapping("/{name}")
+ public ResponseEntity get(@PathVariable String name) {
+ return deployService.getApp(name)
+ .map(ResponseEntity::ok)
+ .orElse(ResponseEntity.notFound().build());
+ }
+
+ @GetMapping("/{name}/logs")
+ public List logs(@PathVariable String name) {
+ return deployService.getBuildLog(name);
+ }
+
+ @PostMapping
+ public ResponseEntity deploy(
+ @RequestParam("name") String name,
+ @RequestParam("jar") MultipartFile jar,
+ @RequestParam(value = "cpuRequest", required = false) String cpuRequest,
+ @RequestParam(value = "memoryRequest", required = false) String memoryRequest,
+ @RequestParam(value = "cpuLimit", required = false) String cpuLimit,
+ @RequestParam(value = "memoryLimit", required = false) String memoryLimit,
+ @RequestParam(value = "envVars", required = false) String envVarsJson
+ ) {
+ try {
+ if (!name.matches("^[a-z0-9]([a-z0-9-]*[a-z0-9])?$")) {
+ return ResponseEntity.badRequest().build();
+ }
+ if (jar.isEmpty() || !jar.getOriginalFilename().endsWith(".jar")) {
+ return ResponseEntity.badRequest().build();
+ }
+
+ Map envVars = Map.of();
+ if (envVarsJson != null && !envVarsJson.isBlank()) {
+ envVars = objectMapper.readValue(envVarsJson, new TypeReference<>() {});
+ }
+
+ var request = new DeployRequest(name, cpuRequest, memoryRequest, cpuLimit, memoryLimit, envVars);
+ var app = deployService.deploy(name, jar, request);
+ return ResponseEntity.accepted().body(app);
+ } catch (IllegalArgumentException e) {
+ return ResponseEntity.badRequest().build();
+ } catch (Exception e) {
+ return ResponseEntity.internalServerError().build();
+ }
+ }
+
+ @DeleteMapping("/{name}")
+ public ResponseEntity undeploy(@PathVariable String name) {
+ try {
+ deployService.undeploy(name);
+ return ResponseEntity.noContent().build();
+ } catch (IllegalArgumentException e) {
+ return ResponseEntity.notFound().build();
+ }
+ }
+
+ @GetMapping("/config")
+ public Map config() {
+ return Map.of("cameleerServerUi", props.getCameleerServerUi());
+ }
+}
diff --git a/src/main/java/com/cameleer/deploy/model/DeployRequest.java b/src/main/java/com/cameleer/deploy/model/DeployRequest.java
new file mode 100644
index 0000000..e2a4a54
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/model/DeployRequest.java
@@ -0,0 +1,12 @@
+package com.cameleer.deploy.model;
+
+import java.util.Map;
+
+public record DeployRequest(
+ String name,
+ String cpuRequest,
+ String memoryRequest,
+ String cpuLimit,
+ String memoryLimit,
+ Map envVars
+) {}
diff --git a/src/main/java/com/cameleer/deploy/model/DeployStatus.java b/src/main/java/com/cameleer/deploy/model/DeployStatus.java
new file mode 100644
index 0000000..4afaade
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/model/DeployStatus.java
@@ -0,0 +1,11 @@
+package com.cameleer.deploy.model;
+
+public enum DeployStatus {
+ BUILDING,
+ PUSHING,
+ DEPLOYING,
+ RUNNING,
+ PENDING,
+ FAILED,
+ DELETED
+}
diff --git a/src/main/java/com/cameleer/deploy/model/DeployedApp.java b/src/main/java/com/cameleer/deploy/model/DeployedApp.java
new file mode 100644
index 0000000..1e87580
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/model/DeployedApp.java
@@ -0,0 +1,23 @@
+package com.cameleer.deploy.model;
+
+import java.time.Instant;
+import java.util.Map;
+
+public record DeployedApp(
+ String name,
+ String imageName,
+ String imageTag,
+ DeployStatus status,
+ String statusMessage,
+ ResourceConfig resources,
+ Map envVars,
+ Instant createdAt
+) {
+ public DeployedApp withStatus(DeployStatus status, String message) {
+ return new DeployedApp(name, imageName, imageTag, status, message, resources, envVars, createdAt);
+ }
+
+ public DeployedApp withImage(String imageName, String imageTag) {
+ return new DeployedApp(name, imageName, imageTag, status, statusMessage, resources, envVars, createdAt);
+ }
+}
diff --git a/src/main/java/com/cameleer/deploy/model/ResourceConfig.java b/src/main/java/com/cameleer/deploy/model/ResourceConfig.java
new file mode 100644
index 0000000..14e1e36
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/model/ResourceConfig.java
@@ -0,0 +1,12 @@
+package com.cameleer.deploy.model;
+
+public record ResourceConfig(
+ String cpuRequest,
+ String memoryRequest,
+ String cpuLimit,
+ String memoryLimit
+) {
+ public static ResourceConfig defaults() {
+ return new ResourceConfig("250m", "256Mi", "500m", "512Mi");
+ }
+}
diff --git a/src/main/java/com/cameleer/deploy/service/DeployService.java b/src/main/java/com/cameleer/deploy/service/DeployService.java
new file mode 100644
index 0000000..e5a22bf
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/service/DeployService.java
@@ -0,0 +1,312 @@
+package com.cameleer.deploy.service;
+
+import com.cameleer.deploy.config.DeployProperties;
+import com.cameleer.deploy.model.*;
+import jakarta.annotation.PostConstruct;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.time.Instant;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+@Service
+public class DeployService {
+
+ private static final Logger log = LoggerFactory.getLogger(DeployService.class);
+
+ private final ConcurrentHashMap apps = new ConcurrentHashMap<>();
+ private final ConcurrentHashMap> buildLogs = new ConcurrentHashMap<>();
+ private final DeployProperties props;
+ private final ShellExecutor shell;
+
+ public DeployService(DeployProperties props, ShellExecutor shell) {
+ this.props = props;
+ this.shell = shell;
+ }
+
+ @PostConstruct
+ public void rediscoverApps() {
+ try {
+ String output = shell.exec("kubectl", "get", "deployments",
+ "-n", props.getDemoNamespace(),
+ "-l", "app.kubernetes.io/managed-by=cameleer-deploy",
+ "-o", "jsonpath={range .items[*]}{.metadata.name}|{.spec.template.spec.containers[0].image}|{.metadata.creationTimestamp}{\"\\n\"}{end}");
+
+ for (String line : output.split("\n")) {
+ line = line.trim();
+ if (line.isEmpty()) continue;
+ String[] parts = line.split("\\|");
+ if (parts.length < 3) continue;
+
+ String name = parts[0];
+ String fullImage = parts[1];
+ String created = parts[2];
+
+ String imageName = fullImage.contains(":") ? fullImage.substring(0, fullImage.lastIndexOf(':')) : fullImage;
+ String imageTag = fullImage.contains(":") ? fullImage.substring(fullImage.lastIndexOf(':') + 1) : "latest";
+
+ apps.put(name, new DeployedApp(
+ name, imageName, imageTag,
+ DeployStatus.RUNNING, null,
+ ResourceConfig.defaults(),
+ Map.of(),
+ Instant.parse(created)
+ ));
+ log.info("Rediscovered app: {}", name);
+ }
+ } catch (Exception e) {
+ log.warn("Could not rediscover apps from namespace {}: {}", props.getDemoNamespace(), e.getMessage());
+ }
+ }
+
+ public List listApps() {
+ reconcileStatuses();
+ return apps.values().stream()
+ .filter(a -> a.status() != DeployStatus.DELETED)
+ .sorted(Comparator.comparing(DeployedApp::createdAt).reversed())
+ .toList();
+ }
+
+ public Optional getApp(String name) {
+ return Optional.ofNullable(apps.get(name));
+ }
+
+ public List getBuildLog(String name) {
+ return buildLogs.getOrDefault(name, List.of());
+ }
+
+ public DeployedApp deploy(String name, MultipartFile jarFile, DeployRequest request) throws IOException {
+ if (apps.containsKey(name) && apps.get(name).status() != DeployStatus.DELETED) {
+ throw new IllegalArgumentException("App '" + name + "' already exists");
+ }
+
+ var resources = new ResourceConfig(
+ request.cpuRequest() != null ? request.cpuRequest() : "250m",
+ request.memoryRequest() != null ? request.memoryRequest() : "256Mi",
+ request.cpuLimit() != null ? request.cpuLimit() : "500m",
+ request.memoryLimit() != null ? request.memoryLimit() : "512Mi"
+ );
+
+ var envVars = request.envVars() != null ? request.envVars() : Map.of();
+ String imageTag = String.valueOf(System.currentTimeMillis());
+ String imageName = props.getRegistry() + "/" + name;
+
+ var app = new DeployedApp(name, imageName, imageTag,
+ DeployStatus.BUILDING, null, resources, envVars, Instant.now());
+ apps.put(name, app);
+ buildLogs.put(name, Collections.synchronizedList(new ArrayList<>()));
+
+ // Run pipeline async
+ Thread.ofVirtual().name("deploy-" + name).start(() -> {
+ try {
+ runPipeline(name, jarFile.getBytes(), imageName, imageTag, resources, envVars);
+ } catch (Exception e) {
+ log.error("Deploy pipeline failed for {}", name, e);
+ appendLog(name, "FATAL: " + e.getMessage());
+ apps.computeIfPresent(name, (k, v) -> v.withStatus(DeployStatus.FAILED, e.getMessage()));
+ }
+ });
+
+ return app;
+ }
+
+ public void undeploy(String name) {
+ var app = apps.get(name);
+ if (app == null) throw new IllegalArgumentException("App '" + name + "' not found");
+
+ try {
+ appendLog(name, "Deleting deployment...");
+ shell.exec("kubectl", "delete", "deployment", name,
+ "-n", props.getDemoNamespace(), "--ignore-not-found=true");
+ appendLog(name, "Deployment deleted.");
+
+ apps.computeIfPresent(name, (k, v) -> v.withStatus(DeployStatus.DELETED, "Undeployed"));
+ } catch (Exception e) {
+ log.error("Failed to undeploy {}", name, e);
+ throw new RuntimeException("Failed to undeploy: " + e.getMessage());
+ }
+ }
+
+ private void runPipeline(String name, byte[] jarBytes, String imageName,
+ String imageTag, ResourceConfig resources,
+ Map envVars) throws Exception {
+ Path buildDir = Files.createTempDirectory("cameleer-build-" + name);
+ try {
+ // 1. Write JAR
+ appendLog(name, "Saving JAR file...");
+ Path jarPath = buildDir.resolve("app.jar");
+ Files.write(jarPath, jarBytes);
+ appendLog(name, "JAR saved (" + jarBytes.length / 1024 + " KB)");
+
+ // 2. Generate Dockerfile
+ appendLog(name, "Generating Dockerfile...");
+ String dockerfile = generateDockerfile(name);
+ Files.writeString(buildDir.resolve("Dockerfile"), dockerfile);
+ appendLog(name, "Dockerfile generated");
+
+ // 3. Build image
+ String fullTag = imageName + ":" + imageTag;
+ appendLog(name, "Building image: " + fullTag);
+ apps.computeIfPresent(name, (k, v) -> v.withStatus(DeployStatus.BUILDING, "docker build"));
+
+ String buildOutput = shell.exec("docker", "build", "-t", fullTag, buildDir.toString());
+ for (String line : buildOutput.split("\n")) {
+ appendLog(name, "[build] " + line);
+ }
+ appendLog(name, "Image built successfully");
+
+ // 4. Push image
+ appendLog(name, "Pushing image to registry...");
+ apps.computeIfPresent(name, (k, v) -> v.withStatus(DeployStatus.PUSHING, "docker push"));
+
+ String pushOutput = shell.exec("docker", "push", fullTag);
+ for (String line : pushOutput.split("\n")) {
+ if (!line.isBlank()) appendLog(name, "[push] " + line);
+ }
+ appendLog(name, "Image pushed successfully");
+
+ // 5. Deploy to K8s
+ appendLog(name, "Deploying to Kubernetes...");
+ apps.computeIfPresent(name, (k, v) -> v.withStatus(DeployStatus.DEPLOYING, "kubectl apply"));
+
+ String manifest = generateManifest(name, fullTag, resources, envVars);
+ Path manifestPath = buildDir.resolve("deployment.yaml");
+ Files.writeString(manifestPath, manifest);
+
+ String applyOutput = shell.exec("kubectl", "apply", "-f", manifestPath.toString());
+ appendLog(name, "[kubectl] " + applyOutput.trim());
+
+ apps.computeIfPresent(name, (k, v) -> v
+ .withImage(imageName, imageTag)
+ .withStatus(DeployStatus.PENDING, "Waiting for pod to start"));
+ appendLog(name, "Deployment applied. Waiting for pod...");
+
+ } finally {
+ // Cleanup
+ try {
+ Files.walk(buildDir)
+ .sorted(Comparator.reverseOrder())
+ .forEach(p -> { try { Files.delete(p); } catch (IOException ignored) {} });
+ } catch (IOException ignored) {}
+ }
+ }
+
+ private String generateDockerfile(String appName) {
+ return """
+ FROM eclipse-temurin:21-jre-alpine
+
+ ADD %s /opt/cameleer/agent.jar
+
+ COPY app.jar /opt/app/app.jar
+
+ ENV CAMELEER_SERVER_URL=%s
+ ENV CAMELEER_APP_NAME=%s
+ ENV CAMELEER_AUTH_TOKEN=%s
+
+ ENTRYPOINT ["java", "-javaagent:/opt/cameleer/agent.jar", "-jar", "/opt/app/app.jar"]
+ """.formatted(
+ props.getAgentMavenUrl(),
+ props.getServerUrl(),
+ appName,
+ props.getBootstrapToken()
+ );
+ }
+
+ private String generateManifest(String name, String image,
+ ResourceConfig resources,
+ Map envVars) {
+ var envLines = new StringBuilder();
+ // Standard cameleer env vars are baked into the image, add user-provided ones
+ for (var entry : envVars.entrySet()) {
+ envLines.append("""
+ - name: %s
+ value: "%s"
+ """.formatted(entry.getKey(), entry.getValue().replace("\"", "\\\"")));
+ }
+
+ return """
+ apiVersion: apps/v1
+ kind: Deployment
+ metadata:
+ name: %s
+ namespace: %s
+ labels:
+ app.kubernetes.io/managed-by: cameleer-deploy
+ cameleer/app-name: %s
+ spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: %s
+ template:
+ metadata:
+ labels:
+ app: %s
+ cameleer/app-name: %s
+ spec:
+ containers:
+ - name: app
+ image: %s
+ resources:
+ requests:
+ cpu: %s
+ memory: %s
+ limits:
+ cpu: %s
+ memory: %s
+ env:
+ %s ports:
+ - containerPort: 8080
+ """.formatted(
+ name, props.getDemoNamespace(), name,
+ name, name, name,
+ image,
+ resources.cpuRequest(), resources.memoryRequest(),
+ resources.cpuLimit(), resources.memoryLimit(),
+ envLines.toString()
+ );
+ }
+
+ private void reconcileStatuses() {
+ for (var entry : apps.entrySet()) {
+ var app = entry.getValue();
+ if (app.status() == DeployStatus.DELETED || app.status() == DeployStatus.BUILDING
+ || app.status() == DeployStatus.PUSHING) {
+ continue;
+ }
+ try {
+ String phase = shell.exec("kubectl", "get", "pods",
+ "-n", props.getDemoNamespace(),
+ "-l", "app=" + app.name(),
+ "-o", "jsonpath={.items[0].status.phase}").trim();
+
+ DeployStatus newStatus = switch (phase) {
+ case "Running" -> DeployStatus.RUNNING;
+ case "Pending" -> DeployStatus.PENDING;
+ case "Failed", "Unknown" -> DeployStatus.FAILED;
+ default -> app.status();
+ };
+
+ if (newStatus != app.status()) {
+ apps.computeIfPresent(entry.getKey(),
+ (k, v) -> v.withStatus(newStatus, phase));
+ }
+ } catch (Exception e) {
+ // Pod might not exist yet
+ }
+ }
+ }
+
+ private void appendLog(String name, String line) {
+ var logList = buildLogs.get(name);
+ if (logList != null) {
+ logList.add("[" + Instant.now() + "] " + line);
+ }
+ }
+}
diff --git a/src/main/java/com/cameleer/deploy/service/ShellExecutor.java b/src/main/java/com/cameleer/deploy/service/ShellExecutor.java
new file mode 100644
index 0000000..14d2e9e
--- /dev/null
+++ b/src/main/java/com/cameleer/deploy/service/ShellExecutor.java
@@ -0,0 +1,32 @@
+package com.cameleer.deploy.service;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Component;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class ShellExecutor {
+
+ private static final Logger log = LoggerFactory.getLogger(ShellExecutor.class);
+
+ public String exec(String... command) throws IOException, InterruptedException {
+ log.debug("Executing: {}", String.join(" ", command));
+ var pb = new ProcessBuilder(command)
+ .redirectErrorStream(true);
+ var process = pb.start();
+ String output = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
+ boolean finished = process.waitFor(5, TimeUnit.MINUTES);
+ if (!finished) {
+ process.destroyForcibly();
+ throw new RuntimeException("Command timed out: " + String.join(" ", command));
+ }
+ if (process.exitValue() != 0) {
+ throw new RuntimeException("Command failed (exit " + process.exitValue() + "): " + output);
+ }
+ return output;
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
new file mode 100644
index 0000000..a6b5e5f
--- /dev/null
+++ b/src/main/resources/application.yml
@@ -0,0 +1,17 @@
+server:
+ port: 8082
+
+spring:
+ servlet:
+ multipart:
+ max-file-size: 200MB
+ max-request-size: 200MB
+
+cameleer:
+ deploy:
+ server-url: ${CAMELEER_SERVER_URL:http://cameleer3-server.cameleer.svc:8081}
+ bootstrap-token: ${CAMELEER_BOOTSTRAP_TOKEN:changeme}
+ registry: ${CAMELEER_REGISTRY:gitea.siegeln.net/cameleer/demo-apps}
+ agent-maven-url: ${CAMELEER_AGENT_MAVEN_URL:https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer3/cameleer3-agent/1.0-SNAPSHOT/cameleer3-agent-1.0-SNAPSHOT.jar}
+ demo-namespace: ${CAMELEER_DEMO_NAMESPACE:cameleer-demo}
+ cameleer-server-ui: ${CAMELEER_SERVER_UI:http://localhost:8081}
diff --git a/target/classes/application.yml b/target/classes/application.yml
new file mode 100644
index 0000000..a6b5e5f
--- /dev/null
+++ b/target/classes/application.yml
@@ -0,0 +1,17 @@
+server:
+ port: 8082
+
+spring:
+ servlet:
+ multipart:
+ max-file-size: 200MB
+ max-request-size: 200MB
+
+cameleer:
+ deploy:
+ server-url: ${CAMELEER_SERVER_URL:http://cameleer3-server.cameleer.svc:8081}
+ bootstrap-token: ${CAMELEER_BOOTSTRAP_TOKEN:changeme}
+ registry: ${CAMELEER_REGISTRY:gitea.siegeln.net/cameleer/demo-apps}
+ agent-maven-url: ${CAMELEER_AGENT_MAVEN_URL:https://gitea.siegeln.net/api/packages/cameleer/maven/com/cameleer3/cameleer3-agent/1.0-SNAPSHOT/cameleer3-agent-1.0-SNAPSHOT.jar}
+ demo-namespace: ${CAMELEER_DEMO_NAMESPACE:cameleer-demo}
+ cameleer-server-ui: ${CAMELEER_SERVER_UI:http://localhost:8081}
diff --git a/target/classes/com/cameleer/deploy/DeployDemoApplication.class b/target/classes/com/cameleer/deploy/DeployDemoApplication.class
new file mode 100644
index 0000000..eeaf070
Binary files /dev/null and b/target/classes/com/cameleer/deploy/DeployDemoApplication.class differ
diff --git a/target/classes/com/cameleer/deploy/config/DeployProperties.class b/target/classes/com/cameleer/deploy/config/DeployProperties.class
new file mode 100644
index 0000000..5558bf6
Binary files /dev/null and b/target/classes/com/cameleer/deploy/config/DeployProperties.class differ
diff --git a/target/classes/com/cameleer/deploy/config/WebConfig.class b/target/classes/com/cameleer/deploy/config/WebConfig.class
new file mode 100644
index 0000000..c59aa8e
Binary files /dev/null and b/target/classes/com/cameleer/deploy/config/WebConfig.class differ
diff --git a/target/classes/com/cameleer/deploy/controller/DeployController$1.class b/target/classes/com/cameleer/deploy/controller/DeployController$1.class
new file mode 100644
index 0000000..0cccb82
Binary files /dev/null and b/target/classes/com/cameleer/deploy/controller/DeployController$1.class differ
diff --git a/target/classes/com/cameleer/deploy/controller/DeployController.class b/target/classes/com/cameleer/deploy/controller/DeployController.class
new file mode 100644
index 0000000..583bab1
Binary files /dev/null and b/target/classes/com/cameleer/deploy/controller/DeployController.class differ
diff --git a/target/classes/com/cameleer/deploy/model/DeployRequest.class b/target/classes/com/cameleer/deploy/model/DeployRequest.class
new file mode 100644
index 0000000..f393136
Binary files /dev/null and b/target/classes/com/cameleer/deploy/model/DeployRequest.class differ
diff --git a/target/classes/com/cameleer/deploy/model/DeployStatus.class b/target/classes/com/cameleer/deploy/model/DeployStatus.class
new file mode 100644
index 0000000..646e7c8
Binary files /dev/null and b/target/classes/com/cameleer/deploy/model/DeployStatus.class differ
diff --git a/target/classes/com/cameleer/deploy/model/DeployedApp.class b/target/classes/com/cameleer/deploy/model/DeployedApp.class
new file mode 100644
index 0000000..0cc9ad2
Binary files /dev/null and b/target/classes/com/cameleer/deploy/model/DeployedApp.class differ
diff --git a/target/classes/com/cameleer/deploy/model/ResourceConfig.class b/target/classes/com/cameleer/deploy/model/ResourceConfig.class
new file mode 100644
index 0000000..b328a8c
Binary files /dev/null and b/target/classes/com/cameleer/deploy/model/ResourceConfig.class differ
diff --git a/target/classes/com/cameleer/deploy/service/DeployService.class b/target/classes/com/cameleer/deploy/service/DeployService.class
new file mode 100644
index 0000000..239352f
Binary files /dev/null and b/target/classes/com/cameleer/deploy/service/DeployService.class differ
diff --git a/target/classes/com/cameleer/deploy/service/ShellExecutor.class b/target/classes/com/cameleer/deploy/service/ShellExecutor.class
new file mode 100644
index 0000000..817fd3f
Binary files /dev/null and b/target/classes/com/cameleer/deploy/service/ShellExecutor.class differ
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..05869aa
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,11 @@
+com\cameleer\deploy\config\WebConfig.class
+com\cameleer\deploy\controller\DeployController$1.class
+com\cameleer\deploy\controller\DeployController.class
+com\cameleer\deploy\DeployDemoApplication.class
+com\cameleer\deploy\model\DeployedApp.class
+com\cameleer\deploy\service\DeployService.class
+com\cameleer\deploy\config\DeployProperties.class
+com\cameleer\deploy\model\DeployStatus.class
+com\cameleer\deploy\service\ShellExecutor.class
+com\cameleer\deploy\model\ResourceConfig.class
+com\cameleer\deploy\model\DeployRequest.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..130c48e
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,10 @@
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\config\DeployProperties.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\config\WebConfig.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\controller\DeployController.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\DeployDemoApplication.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\model\DeployedApp.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\model\DeployRequest.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\model\DeployStatus.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\model\ResourceConfig.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\service\DeployService.java
+C:\Users\Hendrik\Documents\projects\cameleer-deploy-demo\src\main\java\com\cameleer\deploy\service\ShellExecutor.java