Repository: LangStream/langstream (language: python)

Issue:
Packaging of the app occurs before downloading dependencies
I created a pipeline that requires the C* sink, like this:

```
 - name: "Write to AstraDB"
    type: "sink"
    input: "text-chunks"
    configuration:
      connector.class: com.datastax.oss.kafka.sink.CassandraSinkConnector
      key.converter: org.apache.kafka.connect.storage.StringConverter
      value.converter: org.apache.kafka.connect.storage.StringConverter
      cloud.secureConnectBundle: "{{{ secrets.cassandra.secure-connect-bundle }}}"
      auth.username: "{{{ secrets.cassandra.username }}}"
      auth.password: "{{{ secrets.cassandra.password }}}"
      topic.chunks-topic.documents.documents.mapping: "filename=value.filename, chunk_id=value.chunk_id, language=value.language, text=value.text, embeddings_vector=value.embeddings_vector, num_tokens=value.chunk_num_tokens"
      name: cassandra-sink
```
And this:
```
configuration:
  resources:
  - type: "open-ai-configuration"
    name: "OpenAI Azure configuration"
    configuration:
      url: "{{ secrets.open-ai.url }}"
      access-key: "{{ secrets.open-ai.access-key }}"
      provider: "azure"
  dependencies:
    - name: "Kafka Connect Sink for Apache Cassandra from DataStax"
      url: "https://github.com/datastax/kafka-sink/releases/download/1.5.0/kafka-connect-cassandra-sink-1.5.0.jar"
      sha512sum: "242bf60363d36bd426232451cac836a24ae8d510857372a128f601503ad77aa9eabf14c4f484ca0830b6a68d9e8664e3820739ad8dd3deee2c58e49a94a20a3c"
      type: "java-library"
~
```

The first time I deployed the app, the sink went into a crash loop with a `ClassNotFound` error. I shelled into the pod and noticed that the `java/lib` directory that should contain the jar for the C* sink was not in the `/app-code-download` directory.

I deleted the app and redeployed it. The logs messages look like this:

```
chrisbartholomew@cbartholomew-rmbp16 ~/git/streaming-gen-ai -  (main) $  ./bin/sga-cli --conf=conf/cli.yaml apps deploy i-love-ai -app examples/applications/i-love-ai/ -i examples/instances/astra.yaml -s examples/applications/i-love-ai/secrets.yaml
packaging app: /Users/chrisbartholomew/git/streaming-gen-ai/examples/applications/i-love-ai
app packaged
using instance: /Users/chrisbartholomew/git/streaming-gen-ai/examples/instances/astra.yaml
using secrets: /Users/chrisbartholomew/git/streaming-gen-ai/examples/applications/i-love-ai/secrets.yaml
deploying application: i-love-ai (28633 KB)
Dependency: examples/applications/i-love-ai/java/lib/kafka-connect-cassandra-sink-1.5.0.jar at /Users/chrisbartholomew/git/streaming-gen-ai/examples/applications/i-love-ai/java/lib/kafka-connect-cassandra-sink-1.5.0.jar
application i-love-ai deployed
```
From the logs, it looks like "packaging app" is happening before checking the dependency and downloading it. 

The second time I deployed the app, the C* sink worked and the `java/lib` directory was present.




Discussion / hints:

Fix patch:
diff --git a/cli/pom.xml b/cli/pom.xml
index 02a467c54..93a606dbd 100644
--- a/cli/pom.xml
+++ b/cli/pom.xml
@@ -62,8 +62,14 @@
       <version>${project.version}</version>
     </dependency>
     <dependency>
-      <groupId>org.slf4j</groupId>
-      <artifactId>slf4j-nop</artifactId>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>ch.qos.logback</groupId>
+      <artifactId>logback-classic</artifactId>
+      <scope>test</scope>
     </dependency>
     <dependency>
       <groupId>commons-beanutils</groupId>
diff --git a/cli/src/main/java/com/datastax/oss/sga/cli/commands/applications/AbstractDeployApplicationCmd.java b/cli/src/main/java/com/datastax/oss/sga/cli/commands/applications/AbstractDeployApplicationCmd.java
index 289cfada5..7ee5b7182 100644
--- a/cli/src/main/java/com/datastax/oss/sga/cli/commands/applications/AbstractDeployApplicationCmd.java
+++ b/cli/src/main/java/com/datastax/oss/sga/cli/commands/applications/AbstractDeployApplicationCmd.java
@@ -160,11 +160,6 @@ public void run() {
             throw new IllegalArgumentException("no application, instance or secrets file provided");
         }
 
-        final Path tempZip = buildZip(appDirectory, instanceFile, secretsFile, s -> log(s));
-
-        long size = Files.size(tempZip);
-        log("deploying application: %s (%d KB)".formatted(name, size / 1024));
-
         if (appDirectory != null) {
             // parse locally the application in order to validate it
             // and to get the dependencies
@@ -173,6 +168,11 @@ public void run() {
             downloadDependencies(applicationInstance, appDirectory.toPath());
         }
 
+        final Path tempZip = buildZip(appDirectory, instanceFile, secretsFile, s -> log(s));
+
+        long size = Files.size(tempZip);
+        log("deploying application: %s (%d KB)".formatted(name, size / 1024));
+
         String boundary = new BigInteger(256, new Random()).toString();
 
         final String path = tenantAppPath("/" + name);
@@ -217,7 +217,7 @@ private void downloadDependencies(Application applicationInstance, Path director
                     }
                 }
 
-                log("Downloading dependency: %s to %s".formatted(fileName, fileName.toAbsolutePath()));
+                log("downloading dependency: %s to %s".formatted(fileName, fileName.toAbsolutePath()));
                 final HttpRequest request = newDependencyGet(url);
                 http(request, HttpResponse.BodyHandlers.ofFile(fileName));
 


Test patch:
diff --git a/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/AppsCmdTest.java b/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/AppsCmdTest.java
index 3c356108d..2022d93e7 100644
--- a/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/AppsCmdTest.java
+++ b/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/AppsCmdTest.java
@@ -26,6 +26,7 @@
 import com.github.tomakehurst.wiremock.matching.MatchResult;
 import com.github.tomakehurst.wiremock.matching.MultipartValuePatternBuilder;
 import java.io.File;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
@@ -98,6 +99,54 @@ public void testDeploy() throws Exception {
 
     }
 
+
+    @Test
+    public void testDeployWithDependencies() throws Exception {
+
+        final String fileContent = "dep-content";
+        final String fileContentSha = "e1ebfd0f4e4a624eeeffc52c82b048739ea615dca9387630ae7767cb9957aa4ce2cf7afbd032ac8d5fcb73f42316655ea390e37399f14155ed794a6f53c066ec";
+        wireMock.register(WireMock.get("/local/get-dependency.jar")
+                .willReturn(WireMock.ok(fileContent)));
+
+        Path sga = Files.createTempDirectory("sga");
+        final String configurationYamlContent = """
+                configuration:
+                  dependencies:
+                    - name: "PostGRES JDBC Driver"
+                      url: "%s"
+                      sha512sum: "%s"
+                      type: "java-library"
+                """.formatted(wireMockBaseUrl + "/local/get-dependency.jar", fileContentSha);
+        Files.write(Path.of(sga.toFile().getAbsolutePath(), "configuration.yaml"), configurationYamlContent.getBytes(StandardCharsets.UTF_8));
+        final String app = createTempFile("module: module-1", sga);
+        final String instance = createTempFile("instance: {}");
+        final String secrets = createTempFile("secrets: []");
+
+        final InterceptDeploySupport support = new InterceptDeploySupport();
+        wireMock.register(WireMock.post("/api/applications/%s/my-app"
+                        .formatted(TENANT))
+                .withMultipartRequestBody(support.multipartValuePatternBuilder())
+                .willReturn(WireMock.ok("{ \"name\": \"my-app\" }")));
+
+        CommandResult result = executeCommand("apps", "deploy", "my-app", "-s", secrets, "-app", sga.toAbsolutePath().toString(), "-i", instance);
+        Assertions.assertEquals("", result.err());
+        Assertions.assertEquals(0, result.exitCode());
+
+        Assertions.assertEquals(5, support.countFiles());
+        Assertions.assertEquals("module: module-1",
+                support.getFileContent(new File(app).getName()));
+        Assertions.assertEquals("instance: {}",
+                support.getFileContent("instance.yaml"));
+        Assertions.assertEquals("secrets: []",
+                support.getFileContent("secrets.yaml"));
+
+        Assertions.assertEquals(configurationYamlContent,
+                support.getFileContent("configuration.yaml"));
+        Assertions.assertEquals(fileContent,
+                support.getFileContent("java/lib/get-dependency.jar"));
+
+    }
+
     @Test
     public void testUpdate() throws Exception {
         Path sga = Files.createTempDirectory("sga");
diff --git a/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/CommandTestBase.java b/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/CommandTestBase.java
index 110d7afa9..e61711c45 100644
--- a/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/CommandTestBase.java
+++ b/cli/src/test/java/com/datastax/oss/sga/cli/commands/applications/CommandTestBase.java
@@ -37,6 +37,7 @@ public class CommandTestBase {
 
     protected static final String TENANT = "my-tenant";
     protected WireMock wireMock;
+    protected String wireMockBaseUrl;
     protected Path tempDir;
     private Path cliYaml;
 
@@ -51,6 +52,7 @@ public void beforeEach(WireMockRuntimeInfo wmRuntimeInfo, @TempDir Path tempDir)
                 """.formatted(wmRuntimeInfo.getHttpPort(), TENANT);
         Files.write(cliYaml, config.getBytes(StandardCharsets.UTF_8));
         wireMock = wmRuntimeInfo.getWireMock();
+        wireMockBaseUrl = wmRuntimeInfo.getHttpBaseUrl();
     }
 
     protected String createTempFile(String content) {