Skip to content

Commit bef21d2

Browse files
authored
[DXP-1441] streamx init command (#171)
1 parent c7ff666 commit bef21d2

22 files changed

+809
-3
lines changed

core/src/main/java/dev/streamx/cli/StreamxCommand.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import dev.streamx.cli.command.ingestion.publish.PublishCommand;
88
import dev.streamx.cli.command.ingestion.stream.StreamCommand;
99
import dev.streamx.cli.command.ingestion.unpublish.UnpublishCommand;
10+
import dev.streamx.cli.command.init.InitCommand;
1011
import dev.streamx.cli.command.run.RunCommand;
1112
import dev.streamx.cli.config.ArgumentConfigSource;
1213
import dev.streamx.cli.config.validation.ConfigSourcesValidator;
@@ -29,6 +30,7 @@
2930
@Command(mixinStandardHelpOptions = true,
3031
name = "streamx",
3132
subcommands = {
33+
InitCommand.class,
3234
RunCommand.class, DevCommand.class,
3335
PublishCommand.class, UnpublishCommand.class,
3436
BatchCommand.class, StreamCommand.class,

core/src/main/java/dev/streamx/cli/command/cloud/deploy/DeployCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ private void deploy(ServiceMesh serviceMesh, Path projectPath) {
105105
// Collect all resources to delete
106106
ResourceCleaner cleaner = new ResourceCleaner(resourcesToDeploy, managedResources);
107107
kubernetesService.deploy(resourcesToDeploy);
108-
printf("Project %s successfully deployed to '%s' namespace.\n",
108+
printf("Project %s successfully deployed to '%s' namespace.%n",
109109
projectPath.toAbsolutePath().normalize(), kubernetesService.getNamespace());
110110
List<HasMetadata> orphanedResources = cleaner.getOrphanedResources();
111111
printf("Deleting %d orphaned resources.\n", orphanedResources.size());

core/src/main/java/dev/streamx/cli/command/cloud/undeploy/UndeployCommand.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public void run() {
3232
kubernetesService.validateCrdInstallation();
3333
kubernetesService.undeploy(SERVICE_MESH_NAME);
3434

35-
printf("StreamX project successfully undeployed from '%s' namespace.",
35+
printf("StreamX project successfully undeployed from '%s' namespace.%n",
3636
kubernetesService.getNamespace());
3737
}
3838
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package dev.streamx.cli.command.init;
2+
3+
import dev.streamx.cli.exception.GitException;
4+
import dev.streamx.cli.util.os.OsCommandStrategy;
5+
import dev.streamx.cli.util.os.ProcessBuilder;
6+
import jakarta.enterprise.context.ApplicationScoped;
7+
import jakarta.inject.Inject;
8+
import java.io.File;
9+
import java.io.IOException;
10+
import java.nio.file.Path;
11+
import org.apache.commons.io.FileUtils;
12+
import org.jboss.logging.Logger;
13+
14+
@ApplicationScoped
15+
public class GitClient {
16+
17+
@Inject
18+
OsCommandStrategy strategy;
19+
20+
@Inject
21+
Logger logger;
22+
23+
public void clone(String cloneUrl, String outputDir) {
24+
try {
25+
ProcessBuilder processBuilder = strategy.create(
26+
"git clone %s %s".formatted(cloneUrl, outputDir));
27+
28+
processBuilder.addEnv("GIT_TERMINAL_PROMPT", "0");
29+
30+
Process process = processBuilder.start();
31+
process.waitFor();
32+
33+
if (process.exitValue() != 0) {
34+
throw GitException.gitCloneException(process);
35+
}
36+
} catch (IOException | InterruptedException e) {
37+
throw new RuntimeException(e);
38+
}
39+
}
40+
41+
public void removeGitMetadata(String directoryPath) throws IOException {
42+
File directory = Path.of(directoryPath).resolve(".git").toFile();
43+
FileUtils.deleteDirectory(directory);
44+
}
45+
46+
public void validateGitInstalled() {
47+
logger.info("Verifying git installation...");
48+
try {
49+
Process process = strategy.create("git --version").start();
50+
process.waitFor();
51+
byte[] stdOutBytes = process.getInputStream().readAllBytes();
52+
53+
if (process.exitValue() != 0) {
54+
throwGitNotInstalled(process);
55+
} else if (!new String(stdOutBytes).startsWith("git version")) {
56+
throwGitNotInstalled(process);
57+
}
58+
} catch (IOException | InterruptedException e) {
59+
throw new RuntimeException(e);
60+
}
61+
}
62+
63+
private void throwGitNotInstalled(Process process) {
64+
logger.info("Git not installed...");
65+
throw GitException.gitNotInstalledException(process);
66+
}
67+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package dev.streamx.cli.command.init;
2+
3+
import static dev.streamx.cli.util.Output.printf;
4+
5+
import dev.streamx.cli.VersionProvider;
6+
import dev.streamx.cli.command.init.project.template.ProjectTemplateSource;
7+
import dev.streamx.cli.config.ArgumentConfigSource;
8+
import dev.streamx.cli.exception.GitException;
9+
import dev.streamx.cli.util.ExceptionUtils;
10+
import jakarta.inject.Inject;
11+
import java.io.IOException;
12+
import java.nio.file.Files;
13+
import java.nio.file.Path;
14+
import org.jboss.logging.Logger;
15+
import org.jetbrains.annotations.NotNull;
16+
import picocli.CommandLine.Command;
17+
import picocli.CommandLine.Parameters;
18+
19+
@Command(name = InitCommand.COMMAND_NAME,
20+
mixinStandardHelpOptions = true,
21+
versionProvider = VersionProvider.class,
22+
description = "Initialize new StreamX project.")
23+
public class InitCommand implements Runnable {
24+
25+
public static final String COMMAND_NAME = "init";
26+
27+
@Parameters(
28+
paramLabel = "outputDir", description = "path of newly created project",
29+
defaultValue = "streamx-sample-project", arity = "0..1"
30+
)
31+
void outputDir(String outputDir) {
32+
ArgumentConfigSource.registerValue(
33+
InitProjectConfig.STREAMX_INIT_PROJECT_TEMPLATE_OUTPUT_DIR, outputDir);
34+
}
35+
36+
@Inject
37+
InitProjectConfig config;
38+
39+
@Inject
40+
ProjectTemplateSource projectTemplateSource;
41+
42+
@Inject
43+
Logger logger;
44+
45+
@Inject
46+
GitClient gitClient;
47+
48+
@Override
49+
public void run() {
50+
try {
51+
gitClient.validateGitInstalled();
52+
validateOutputDir();
53+
54+
String outputDir = normalizeOutputDirPath(Path.of(config.outputDir()));
55+
printf("Initializing StreamX project...%n");
56+
gitClient.clone(projectTemplateSource.getRepoUrl(), outputDir);
57+
gitClient.removeGitMetadata(outputDir);
58+
printf("Project is ready in '%s'.%n", outputDir);
59+
} catch (GitException e) {
60+
logGitOutput(e);
61+
throw throwGenericException(e);
62+
} catch (Exception e) {
63+
throw throwGenericException(e);
64+
}
65+
}
66+
67+
private void validateOutputDir() {
68+
Path path = Path.of(config.outputDir());
69+
String outputDir = normalizeOutputDirPath(path);
70+
logger.infov("Verifying output directory: {0}", outputDir);
71+
if (Files.exists(path)) {
72+
throw new RuntimeException(outputDir + " already exists");
73+
}
74+
}
75+
76+
private static @NotNull String normalizeOutputDirPath(Path path) {
77+
Path outputDirPath = path.normalize().toAbsolutePath();
78+
return outputDirPath.toString();
79+
}
80+
81+
private void logGitOutput(GitException e) {
82+
try {
83+
String stdOut = new String(e.getProcess().getInputStream().readAllBytes());
84+
logger.infov("Git command stdOut: \n{0}", stdOut, e);
85+
String stdErr = new String(e.getProcess().getErrorStream().readAllBytes());
86+
logger.errorv("Git command stdErr: \n{0}", stdErr, e);
87+
} catch (IOException ex) {
88+
throw new RuntimeException(ex);
89+
}
90+
}
91+
92+
private RuntimeException throwGenericException(Exception e) {
93+
return new RuntimeException(
94+
ExceptionUtils.appendLogSuggestion(
95+
"Unable to initialize new project.\n"
96+
+ "\n"
97+
+ "Details:\n"
98+
+ e.getMessage()), e);
99+
}
100+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dev.streamx.cli.command.init;
2+
3+
import io.smallrye.config.ConfigMapping;
4+
import io.smallrye.config.WithDefault;
5+
import io.smallrye.config.WithName;
6+
7+
@ConfigMapping
8+
public interface InitProjectConfig {
9+
10+
String STREAMX_INIT_PROJECT_TEMPLATE_OUTPUT_DIR =
11+
"streamx.cli.init.project.template.output-dir";
12+
13+
@WithName(STREAMX_INIT_PROJECT_TEMPLATE_OUTPUT_DIR)
14+
@WithDefault("streamx-sample-project")
15+
String outputDir();
16+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package dev.streamx.cli.command.init.project.template;
2+
3+
public class ConfigurableProjectTemplateSource implements ProjectTemplateSource {
4+
5+
private final String projectTemplateUrl;
6+
7+
public ConfigurableProjectTemplateSource(String projectTemplateUrl) {
8+
this.projectTemplateUrl = projectTemplateUrl;
9+
}
10+
11+
@Override
12+
public String getRepoUrl() {
13+
return projectTemplateUrl;
14+
}
15+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package dev.streamx.cli.command.init.project.template;
2+
3+
public class ProdProjectTemplateSource implements ProjectTemplateSource {
4+
5+
public static final String PROD_PROJECT_TEMPLATE_SOURCE_URL =
6+
"[email protected]:streamx-dev/streamx-sample-project.git";
7+
8+
@Override
9+
public String getRepoUrl() {
10+
return PROD_PROJECT_TEMPLATE_SOURCE_URL;
11+
}
12+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package dev.streamx.cli.command.init.project.template;
2+
3+
import static dev.streamx.cli.command.init.project.template.ProdProjectTemplateSource.PROD_PROJECT_TEMPLATE_SOURCE_URL;
4+
5+
import io.quarkus.arc.DefaultBean;
6+
import io.quarkus.arc.profile.IfBuildProfile;
7+
import jakarta.enterprise.context.Dependent;
8+
import jakarta.enterprise.inject.Produces;
9+
import org.eclipse.microprofile.config.inject.ConfigProperty;
10+
11+
@Dependent
12+
public class ProjectTemplateBeanConfiguration {
13+
14+
public static final String REPO_URL = "streamx.cli.init.project.template.repo-url";
15+
16+
@Produces
17+
@IfBuildProfile("prod")
18+
ProjectTemplateSource prodTemplateSource() {
19+
return new ProdProjectTemplateSource();
20+
}
21+
22+
@Produces
23+
@DefaultBean
24+
ProjectTemplateSource configurableTemplateSource(
25+
@ConfigProperty(name = REPO_URL,
26+
defaultValue = PROD_PROJECT_TEMPLATE_SOURCE_URL)
27+
String url
28+
) {
29+
return new ConfigurableProjectTemplateSource(url);
30+
}
31+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package dev.streamx.cli.command.init.project.template;
2+
3+
public interface ProjectTemplateSource {
4+
5+
String getRepoUrl();
6+
}

0 commit comments

Comments
 (0)