Skip to content

Commit

Permalink
feat(cli): provide descriptive server start error
Browse files Browse the repository at this point in the history
* The validation is based just on 3 basic Kestra properties.
* Provided a description and link to the docs in the error.
* The `server local` nor any other non-`server` commands were affected.
* Added a crude test.
  • Loading branch information
yuri1969 committed Jul 4, 2024
1 parent abc2ffc commit 11b93a4
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 13 deletions.
52 changes: 39 additions & 13 deletions cli/src/main/java/io/kestra/cli/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.kestra.cli.commands.flows.FlowCommand;
import io.kestra.cli.commands.namespaces.NamespaceCommand;
import io.kestra.cli.commands.plugins.PluginCommand;
import io.kestra.cli.commands.servers.AbstractServerCommand;
import io.kestra.cli.commands.servers.ServerCommand;
import io.kestra.cli.commands.sys.SysCommand;
import io.kestra.cli.commands.templates.TemplateCommand;
Expand All @@ -13,16 +14,14 @@
import io.micronaut.context.ApplicationContextBuilder;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.Introspected;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.bridge.SLF4JBridgeHandler;
import picocli.CommandLine;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.*;
import java.util.concurrent.Callable;

@CommandLine.Command(
Expand All @@ -44,6 +43,7 @@
NamespaceCommand.class,
}
)
@Slf4j
@Introspected
public class App implements Callable<Integer> {
public static void main(String[] args) {
Expand Down Expand Up @@ -79,9 +79,7 @@ protected static void execute(Class<?> cls, String... args) {
* @param args args passed to java app
* @return the application context created
*/
protected static ApplicationContext applicationContext(Class<?> mainClass,
String[] args) {

protected static ApplicationContext applicationContext(Class<?> mainClass, String[] args) {
ApplicationContextBuilder builder = ApplicationContext
.builder()
.mainClass(mainClass)
Expand All @@ -96,15 +94,15 @@ protected static ApplicationContext applicationContext(Class<?> mainClass,
Class<?> cls = commandLine.getCommandSpec().userObject().getClass();

if (AbstractCommand.class.isAssignableFrom(cls)) {
// if class have propertiesFromConfig, add configuration files
builder.properties(getPropertiesFromMethod(cls, "propertiesFromConfig", commandLine.getCommandSpec().userObject()));

Map<String, Object> properties = new HashMap<>();

// if class have propertiesFromConfig, add configuration files
addPropertiesFromMethod(properties, cls, "propertiesFromConfig", commandLine.getCommandSpec().userObject());
// if class have propertiesOverrides, add force properties for this class
Map<String, Object> propertiesOverrides = getPropertiesFromMethod(cls, "propertiesOverrides", null);
if (propertiesOverrides != null) {
properties.putAll(propertiesOverrides);
addPropertiesFromMethod(properties, cls, "propertiesOverrides", null);

if (AbstractServerCommand.class.isAssignableFrom(cls)) {
validateServerCmdConfig(properties);
}

// custom server configuration
Expand All @@ -123,6 +121,34 @@ protected static ApplicationContext applicationContext(Class<?> mainClass,
return builder.build();
}

private static void addPropertiesFromMethod(Map<String, Object> properties, Class<?> cls, String methodName, Object instance) {
Map<String, Object> propertiesFromMethod = getPropertiesFromMethod(cls, methodName, instance);
if (propertiesFromMethod != null) {
properties.putAll(propertiesFromMethod);
}
}

private static void validateServerCmdConfig(Map<String, Object> propertiesFromConfig) {
final String docsLink = "https://kestra.io/docs/configuration-guide/setup";
final List<String> requiredProperties = List.of(
"kestra.queue.type",
"kestra.repository.type",
"kestra.storage.type"
);

Optional<String> missingKey = requiredProperties.stream()
.filter((property) -> !propertiesFromConfig.containsKey(property))
.findFirst();

if (missingKey.isPresent()) {
log.error("""
Server configuration requires following components to be defined - the Internal Storage, the Queue, and the Repository.
For more details, please follow the official configuration guide at: {}
""", docsLink);
throw new AbstractServerCommand.ServerCommandException("Incomplete server configuration - missing required properties");
}
}

@SuppressWarnings("unchecked")
private static <T> T getPropertiesFromMethod(Class<?> cls, String methodName, Object instance) {
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@
import io.kestra.cli.AbstractCommand;
import picocli.CommandLine;

import java.io.Serial;

abstract public class AbstractServerCommand extends AbstractCommand implements ServerCommandInterface {
@CommandLine.Option(names = {"--port"}, description = "the port to bind")
Integer serverPort;

public static class ServerCommandException extends RuntimeException {
@Serial
private static final long serialVersionUID = 1L;

public ServerCommandException(String errorMessage) {
super(errorMessage);
}
}
}
20 changes: 20 additions & 0 deletions cli/src/test/java/AppTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import io.kestra.cli.commands.servers.AbstractServerCommand;
import io.micronaut.configuration.picocli.PicocliRunner;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.env.Environment;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import io.kestra.cli.App;

Expand All @@ -9,8 +12,20 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class AppTest {
@BeforeAll
public static void setUp() {