Skip to content
This repository has been archived by the owner on Aug 25, 2024. It is now read-only.

Commit

Permalink
Add dry-run apps deployment (LangStream#533)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoloboschi authored Oct 6, 2023
1 parent f5d5499 commit 7979db4
Show file tree
Hide file tree
Showing 12 changed files with 345 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,16 +181,22 @@ public Applications applications() {
}

private class ApplicationsImpl implements Applications {
@Override
public String deploy(String application, MultiPartBodyPublisher multiPartBodyPublisher) {
return deploy(application, multiPartBodyPublisher, false);
}

@Override
@SneakyThrows
public void deploy(String application, MultiPartBodyPublisher multiPartBodyPublisher) {
final String path = tenantAppPath("/" + application);
public String deploy(
String application, MultiPartBodyPublisher multiPartBodyPublisher, boolean dryRun) {
final String path = tenantAppPath("/" + application) + "?dry-run=" + dryRun;
final String contentType =
String.format(
"multipart/form-data; boundary=%s",
multiPartBodyPublisher.getBoundary());
final HttpRequest request = newPost(path, contentType, multiPartBodyPublisher.build());
http(request);
return http(request).body();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
import java.net.http.HttpResponse;

public interface Applications {
void deploy(String application, MultiPartBodyPublisher multiPartBodyPublisher);
String deploy(String application, MultiPartBodyPublisher multiPartBodyPublisher);

String deploy(
String application, MultiPartBodyPublisher multiPartBodyPublisher, boolean dryRun);

void update(String application, MultiPartBodyPublisher multiPartBodyPublisher);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ public ApplicationDescription(
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class ApplicationDefinition {

private ApplicationDefinition(Application application) {
public ApplicationDefinition(Application application) {
this.resources = application.getResources();
this.modules =
application.getModules().values().stream().map(ModuleDefinition::new).toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ public static class DeployApplicationCmd extends AbstractDeployApplicationCmd {
description = "Secrets file path")
private String secretFilePath;

@CommandLine.Option(
names = {"--dry-run"},
description =
"Dry-run mode. Do not deploy the application but only resolves placeholders and display the result.")
private boolean dryRun;

@CommandLine.Option(
names = {"-o"},
description = "Output format for dry-run mode.")
private Formats format = Formats.raw;

@Override
String applicationId() {
return name;
Expand All @@ -79,6 +90,16 @@ String secretFilePath() {
boolean isUpdate() {
return false;
}

@Override
boolean isDryRun() {
return dryRun;
}

@Override
Formats format() {
return format;
}
}

@CommandLine.Command(name = "update", header = "Update an existing LangStream application")
Expand Down Expand Up @@ -126,6 +147,16 @@ String secretFilePath() {
boolean isUpdate() {
return true;
}

@Override
boolean isDryRun() {
return false;
}

@Override
Formats format() {
return null;
}
}

abstract String applicationId();
Expand All @@ -138,6 +169,10 @@ boolean isUpdate() {

abstract boolean isUpdate();

abstract boolean isDryRun();

abstract Formats format();

@Override
@SneakyThrows
public void run() {
Expand All @@ -160,19 +195,15 @@ public void run() {
final Path tempZip = buildZip(appDirectory, this::log);

long size = Files.size(tempZip);
log(String.format("deploying application: %s (%d KB)", applicationId, size / 1024));
String secretsContents = null;
String instanceContents = null;

final Map<String, Object> contents = new HashMap<>();
contents.put("app", tempZip);
if (instanceFile != null) {
try {
contents.put(
"instance",
instanceContents =
LocalFileReferenceResolver.resolveFileReferencesInYAMLFile(
instanceFile.toPath()));
LocalFileReferenceResolver.resolveFileReferencesInYAMLFile(
instanceFile.toPath()));
} catch (Exception e) {
log(
"Failed to resolve instance file references. Please double check the file path: "
Expand All @@ -185,9 +216,8 @@ public void run() {
try {
contents.put(
"secrets",
secretsContents =
LocalFileReferenceResolver.resolveFileReferencesInYAMLFile(
secretsFile.toPath()));
LocalFileReferenceResolver.resolveFileReferencesInYAMLFile(
secretsFile.toPath()));
} catch (Exception e) {
log(
"Failed to resolve secrets file references. Please double check the file path: "
Expand All @@ -199,11 +229,27 @@ public void run() {
final MultiPartBodyPublisher bodyPublisher = buildMultipartContentForAppZip(contents);

if (isUpdate()) {
log(String.format("updating application: %s (%d KB)", applicationId, size / 1024));
getClient().applications().update(applicationId, bodyPublisher);
log(String.format("application %s updated", applicationId));
} else {
getClient().applications().deploy(applicationId, bodyPublisher);
log(String.format("application %s deployed", applicationId));
final boolean dryRun = isDryRun();
if (dryRun) {
log(
String.format(
"resolving application: %s. Dry run mode is enabled, the application will NOT be deployed",
applicationId));
} else {
log(String.format("deploying application: %s (%d KB)", applicationId, size / 1024));
}
final String response =
getClient().applications().deploy(applicationId, bodyPublisher, dryRun);
if (dryRun) {
final Formats format = format();
print(format == Formats.raw ? Formats.yaml : format, response, null, null);
} else {
log(String.format("application %s deployed", applicationId));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ public class LocalRunApplicationCmd extends BaseDockerCmd {
description = "Docker image of the LangStream runtime to use")
private String dockerImageName;

@CommandLine.Option(
names = {"--dry-run"},
description =
"Dry-run mode. Do not deploy the application but only resolves placeholders and display the result.")
private boolean dryRun;

@Override
@SneakyThrows
public void run() {
Expand All @@ -120,6 +126,10 @@ public void run() {
dockerImageName = "ghcr.io/langstream/langstream-runtime-tester";
}
}
startBroker = !dryRun && startBroker;
startDatabase = !dryRun && startDatabase;
startS3 = !dryRun && startS3;
startWebservices = !dryRun && startWebservices;

final File appDirectory = checkFileExistsOrDownload(appPath);
final File instanceFile;
Expand Down Expand Up @@ -170,7 +180,7 @@ public void run() {
throw e;
}
} else {
if (startBroker) {
if (startBroker || dryRun) {
instanceContents =
"instance:\n"
+ " streamingCluster:\n"
Expand Down Expand Up @@ -211,7 +221,8 @@ public void run() {
startBroker,
startS3,
startWebservices,
startDatabase);
startDatabase,
dryRun);
}

private void executeOnDocker(
Expand All @@ -224,7 +235,8 @@ private void executeOnDocker(
boolean startBroker,
boolean startS3,
boolean startWebservices,
boolean startDatabase)
boolean startDatabase,
boolean dryRun)
throws Exception {
File tmpInstanceFile = Files.createTempFile("instance", ".yaml").toFile();
Files.write(tmpInstanceFile.toPath(), instanceContents.getBytes(StandardCharsets.UTF_8));
Expand All @@ -251,6 +263,8 @@ private void executeOnDocker(
commandLine.add("LANSGSTREAM_TESTER_APPLICATIONID=" + applicationId);
commandLine.add("-e");
commandLine.add("LANSGSTREAM_TESTER_STARTWEBSERVICES=" + startWebservices);
commandLine.add("-e");
commandLine.add("LANSGSTREAM_TESTER_DRYRUN=" + dryRun);

if (singleAgentId != null && !singleAgentId.isEmpty()) {
commandLine.add("-e");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public void testDeploy() throws Exception {
AbstractDeployApplicationCmd.buildZip(langstream.toFile(), System.out::println);

wireMock.register(
WireMock.post(String.format("/api/applications/%s/my-app", TENANT))
WireMock.post(String.format("/api/applications/%s/my-app?dry-run=false", TENANT))
.withMultipartRequestBody(
aMultipart("app")
.withBody(binaryEqualTo(Files.readAllBytes(zipFile))))
Expand Down Expand Up @@ -108,7 +108,7 @@ public void testDeployWithDependencies() throws Exception {
final Path zipFile =
AbstractDeployApplicationCmd.buildZip(langstream.toFile(), System.out::println);
wireMock.register(
WireMock.post(String.format("/api/applications/%s/my-app", TENANT))
WireMock.post(String.format("/api/applications/%s/my-app?dry-run=false", TENANT))
.withMultipartRequestBody(
aMultipart("app")
.withBody(binaryEqualTo(Files.readAllBytes(zipFile))))
Expand Down Expand Up @@ -168,6 +168,62 @@ public void testUpdateAll() throws Exception {
Assertions.assertEquals("", result.err());
}

@Test
public void testDeployDryRun() throws Exception {
Path langstream = Files.createTempDirectory("langstream");
final String app = createTempFile("module: module-1", langstream);
final String instance = createTempFile("instance: {}");
final String secrets = createTempFile("secrets: []");

final Path zipFile =
AbstractDeployApplicationCmd.buildZip(langstream.toFile(), System.out::println);

wireMock.register(
WireMock.post(String.format("/api/applications/%s/my-app?dry-run=true", TENANT))
.withMultipartRequestBody(
aMultipart("app")
.withBody(binaryEqualTo(Files.readAllBytes(zipFile))))
.withMultipartRequestBody(
aMultipart("instance").withBody(equalTo("instance: {}")))
.withMultipartRequestBody(
aMultipart("secrets").withBody(equalTo("secrets: []")))
.willReturn(WireMock.ok("{ \"name\": \"my-app\" }")));

CommandResult result =
executeCommand(
"apps",
"deploy",
"my-app",
"-s",
secrets,
"-app",
langstream.toAbsolutePath().toString(),
"-i",
instance,
"--dry-run");
Assertions.assertEquals(0, result.exitCode());
Assertions.assertEquals("", result.err());
Assertions.assertTrue(result.out().contains("name: \"my-app\""));

result =
executeCommand(
"apps",
"deploy",
"my-app",
"-s",
secrets,
"-app",
langstream.toAbsolutePath().toString(),
"-i",
instance,
"--dry-run",
"-o",
"json");
Assertions.assertEquals(0, result.exitCode());
Assertions.assertEquals("", result.err());
Assertions.assertTrue(result.out().contains("{\n" + " \"name\" : \"my-app\"\n" + "}"));
}

@Test
public void testUpdateInstance() throws Exception {
final String instance = createTempFile("instance: {}");
Expand Down Expand Up @@ -399,7 +455,7 @@ public void testDeployWithFilePlaceholders() throws Exception {
AbstractDeployApplicationCmd.buildZip(langstream.toFile(), System.out::println);

wireMock.register(
WireMock.post(String.format("/api/applications/%s/my-app", TENANT))
WireMock.post(String.format("/api/applications/%s/my-app?dry-run=false", TENANT))
.withMultipartRequestBody(
aMultipart("app")
.withBody(binaryEqualTo(Files.readAllBytes(zipFile))))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ if [ "$START_HERDDB" = "true" ]; then
/herddb/herddb/bin/service server start
fi

exec java ${JAVA_OPTS} -Dlogging.config=/app/logback.xml -Djdk.lang.Process.launchMechanism=vfork -cp "/app/lib/*:/app/tester/lib/*" "ai.langstream.runtime.tester.Main"
exec java ${JAVA_OPTS} -D -Dlogback.configurationFile=/app/logback.xml -Djdk.lang.Process.launchMechanism=vfork -cp "/app/lib/*" "ai.langstream.runtime.tester.Main"
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ RUN chmod -R g+w /kafka \
&& chown 10000:0 -R /herddb

# Add the runtime code at the end. This optimizes docker layers to not depend on artifacts-specific changes.
ADD maven/lib /app/tester/lib
RUN rm -f /app/tester/lib/*netty*
RUN rm -rf /app/lib
ADD maven/lib /app/lib
RUN rm -f /app/lib/*netty*
ADD maven/entrypoint.sh /app/entrypoint.sh
ADD maven/logback.xml /app/logback.xml

Expand Down
Loading

0 comments on commit 7979db4

Please sign in to comment.