Skip to content

Commit 20fc773

Browse files
authored
feat(core): add config flag to disable the UI and run the webserver API-only (#16748)
Add kestra.webserver.ui.enabled (default true). Setting it to false disables the bundled UI: the /ui/** static-resources route is turned off via Micronaut's StaticResourceConfiguration.enabled, and the / redirect and index.html rewriting filter are not registered. The API (/api/v1/**, /ping) keeps working. A positively-named flag lets the static route be disabled with pure configuration (enabled = ${kestra.webserver.ui.enabled:true}), so no short-circuiting filter is needed. Closes #16747
1 parent 7cad37a commit 20fc773

6 files changed

Lines changed: 116 additions & 0 deletions

File tree

cli/src/main/resources/application.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ micronaut:
1212
ui:
1313
paths: classpath:ui
1414
mapping: /ui/**
15+
enabled: ${kestra.webserver.ui.enabled:true}
1516
static:
1617
paths: classpath:static
1718
mapping: /static/**

webserver/src/main/java/io/kestra/webserver/controllers/api/RedirectController.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.net.URI;
44

5+
import io.micronaut.context.annotation.Requires;
56
import io.micronaut.context.annotation.Value;
67
import io.micronaut.core.annotation.Nullable;
78
import io.micronaut.http.HttpResponse;
@@ -12,6 +13,7 @@
1213

1314
@Slf4j
1415
@Controller
16+
@Requires(property = "kestra.webserver.ui.enabled", notEquals = "false", defaultValue = "true")
1517
public class RedirectController {
1618
@Nullable
1719
@Value("${micronaut.server.context-path}")

webserver/src/main/java/io/kestra/webserver/controllers/api/StaticFilter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import io.kestra.webserver.configuration.WebserverConfiguration;
1414

15+
import io.micronaut.context.annotation.Requires;
1516
import io.micronaut.context.annotation.Value;
1617
import io.micronaut.core.annotation.Nullable;
1718
import jakarta.inject.Inject;
@@ -33,6 +34,7 @@
3334
import static io.kestra.core.utils.Rethrow.throwFunction;
3435

3536
@Filter("/ui/**")
37+
@Requires(property = "kestra.webserver.ui.enabled", notEquals = "false", defaultValue = "true")
3638
public class StaticFilter implements HttpServerFilter {
3739
@Nullable
3840
@Value("${micronaut.server.context-path}")
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package io.kestra.webserver.controllers.api;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import io.kestra.core.junit.annotations.KestraTest;
6+
7+
import io.micronaut.context.ApplicationContext;
8+
import io.micronaut.context.annotation.Property;
9+
import io.micronaut.http.HttpStatus;
10+
import io.micronaut.http.client.annotation.Client;
11+
import io.micronaut.http.client.exceptions.HttpClientResponseException;
12+
import io.micronaut.reactor.http.client.ReactorHttpClient;
13+
import jakarta.inject.Inject;
14+
15+
import static io.micronaut.http.HttpRequest.GET;
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
18+
19+
@KestraTest
20+
@Property(name = "kestra.webserver.ui.enabled", value = "false")
21+
class UiDisabledWebserverTest {
22+
@Inject
23+
@Client("/")
24+
ReactorHttpClient client;
25+
26+
@Inject
27+
ApplicationContext applicationContext;
28+
29+
@Test
30+
void shouldNotServeUiWhenUiIsDisabled() {
31+
// When / Then
32+
assertThatThrownBy(() -> client.toBlocking().exchange(GET("/ui/")))
33+
.isInstanceOf(HttpClientResponseException.class)
34+
.extracting(e -> ((HttpClientResponseException) e).getStatus())
35+
.isEqualTo(HttpStatus.NOT_FOUND);
36+
}
37+
38+
@Test
39+
void shouldNotRedirectRootToUiWhenUiIsDisabled() {
40+
// When / Then
41+
assertThatThrownBy(() -> client.toBlocking().exchange(GET("/")))
42+
.isInstanceOf(HttpClientResponseException.class)
43+
.extracting(e -> ((HttpClientResponseException) e).getStatus())
44+
.isEqualTo(HttpStatus.NOT_FOUND);
45+
}
46+
47+
@Test
48+
void shouldKeepApiAvailableWhenUiIsDisabled() {
49+
// When
50+
String response = client.toBlocking().retrieve(GET("/ping"));
51+
52+
// Then
53+
assertThat(response).isEqualTo("pong");
54+
}
55+
56+
@Test
57+
void shouldNotRegisterUiBeansWhenUiIsDisabled() {
58+
// Then
59+
assertThat(applicationContext.containsBean(RedirectController.class)).isFalse();
60+
assertThat(applicationContext.containsBean(StaticFilter.class)).isFalse();
61+
}
62+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.kestra.webserver.controllers.api;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import io.kestra.core.junit.annotations.KestraTest;
6+
7+
import io.micronaut.context.ApplicationContext;
8+
import io.micronaut.http.HttpResponse;
9+
import io.micronaut.http.HttpStatus;
10+
import io.micronaut.http.client.DefaultHttpClientConfiguration;
11+
import io.micronaut.http.client.HttpClient;
12+
import io.micronaut.runtime.server.EmbeddedServer;
13+
import jakarta.inject.Inject;
14+
15+
import static io.micronaut.http.HttpRequest.GET;
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
18+
@KestraTest
19+
class UiEnabledWebserverTest {
20+
@Inject
21+
EmbeddedServer embeddedServer;
22+
23+
@Inject
24+
ApplicationContext applicationContext;
25+
26+
@Test
27+
void shouldRedirectRootToUiByDefault() {
28+
// Given - a client that does not follow redirects
29+
DefaultHttpClientConfiguration configuration = new DefaultHttpClientConfiguration();
30+
configuration.setFollowRedirects(false);
31+
32+
try (HttpClient client = HttpClient.create(embeddedServer.getURL(), configuration)) {
33+
// When
34+
HttpResponse<?> response = client.toBlocking().exchange(GET("/"));
35+
36+
// Then
37+
assertThat(response.getStatus().getCode()).isEqualTo(HttpStatus.TEMPORARY_REDIRECT.getCode());
38+
assertThat(response.header("Location")).isEqualTo("/ui/");
39+
}
40+
}
41+
42+
@Test
43+
void shouldRegisterUiBeansByDefault() {
44+
// Then
45+
assertThat(applicationContext.containsBean(RedirectController.class)).isTrue();
46+
assertThat(applicationContext.containsBean(StaticFilter.class)).isTrue();
47+
}
48+
}

webserver/src/test/resources/application-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ micronaut:
1111
ui:
1212
paths: classpath:ui
1313
mapping: /ui/**
14+
enabled: ${kestra.webserver.ui.enabled:true}
1415

1516
http:
1617
client:

0 commit comments

Comments
 (0)