diff --git a/cli/src/main/java/io/kestra/cli/App.java b/cli/src/main/java/io/kestra/cli/App.java index cc6b4ebd1b..1ec2797f32 100644 --- a/cli/src/main/java/io/kestra/cli/App.java +++ b/cli/src/main/java/io/kestra/cli/App.java @@ -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; @@ -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( @@ -44,6 +43,7 @@ NamespaceCommand.class, } ) +@Slf4j @Introspected public class App implements Callable { public static void main(String[] args) { @@ -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) @@ -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 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 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 @@ -123,6 +121,34 @@ protected static ApplicationContext applicationContext(Class mainClass, return builder.build(); } + private static void addPropertiesFromMethod(Map properties, Class cls, String methodName, Object instance) { + Map propertiesFromMethod = getPropertiesFromMethod(cls, methodName, instance); + if (propertiesFromMethod != null) { + properties.putAll(propertiesFromMethod); + } + } + + private static void validateServerCmdConfig(Map propertiesFromConfig) { + final Map requiredProperties = Map.of( + "kestra.queue.type", "https://kestra.io/docs/configuration-guide/setup#queue-configuration", + "kestra.repository.type", "https://kestra.io/docs/configuration-guide/setup#repository-configuration", + "kestra.storage.type", "https://kestra.io/docs/configuration-guide/setup#internal-storage-configuration" + ); + + final List> missingProperties = requiredProperties.entrySet().stream() + .filter((property) -> !propertiesFromConfig.containsKey(property.getKey())) + .toList(); + + missingProperties.forEach(property -> log.error(""" + Server configuration requires the '{}' property to be defined. + For more details, please follow the official setup guide at: {}""", property.getKey(), property.getValue()) + ); + + if (!missingProperties.isEmpty()) { + throw new AbstractServerCommand.ServerCommandException("Incomplete server configuration - missing required properties"); + } + } + @SuppressWarnings("unchecked") private static T getPropertiesFromMethod(Class cls, String methodName, Object instance) { try { diff --git a/cli/src/main/java/io/kestra/cli/commands/servers/AbstractServerCommand.java b/cli/src/main/java/io/kestra/cli/commands/servers/AbstractServerCommand.java index 5e84f44510..68c5798c34 100644 --- a/cli/src/main/java/io/kestra/cli/commands/servers/AbstractServerCommand.java +++ b/cli/src/main/java/io/kestra/cli/commands/servers/AbstractServerCommand.java @@ -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); + } + } } diff --git a/cli/src/test/java/AppTest.java b/cli/src/test/java/AppTest.java index f338283b99..0db7c4cf35 100644 --- a/cli/src/test/java/AppTest.java +++ b/cli/src/test/java/AppTest.java @@ -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; @@ -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() { + // Don't make any assumptions about $HOME + System.setProperty("user.home", "/foo"); + } + + @AfterAll + public static void tearDown() { + System.clearProperty("user.home"); + } + @Test public void testHelp() { ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -22,4 +37,9 @@ public void testHelp() { assertThat(out.toString(), containsString("kestra")); } } + + @Test + public void testSeverCommandValidation() { + assertThrows(AbstractServerCommand.ServerCommandException.class, () -> App.main(new String[]{"server", "webserver"})); + } }