mirror of
https://github.com/kestra-io/kestra.git
synced 2025-12-25 11:12:12 -05:00
Compare commits
17 Commits
fix/failin
...
v0.16.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c757827b9d | ||
|
|
2a5c82b2a3 | ||
|
|
15bb0ee65b | ||
|
|
d06e8dad6e | ||
|
|
e9f5752278 | ||
|
|
c16c5ddaf5 | ||
|
|
b706ca1911 | ||
|
|
366246e0a8 | ||
|
|
dcea4551cc | ||
|
|
c0ff6fcc52 | ||
|
|
0525e7eaca | ||
|
|
d1fb098f5b | ||
|
|
9703cc48cb | ||
|
|
31c3e5a4f6 | ||
|
|
bda52eb49d | ||
|
|
8a54b8ec7f | ||
|
|
c34c82c1f9 |
@@ -197,6 +197,7 @@ subprojects {
|
||||
environment 'SECRET_NEW_LINE', "cGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2\nZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZleXJsb25ncGFzc3dvcmR2ZXJ5dmVyeXZl\neXJsb25n"
|
||||
environment 'SECRET_WEBHOOK_KEY', "secretKey".bytes.encodeBase64().toString()
|
||||
environment 'SECRET_NON_B64_SECRET', "some secret value"
|
||||
environment 'SECRET_PASSWORD', "cGFzc3dvcmQ="
|
||||
environment 'KESTRA_TEST1', "true"
|
||||
environment 'KESTRA_TEST2', "Pass by env"
|
||||
}
|
||||
|
||||
@@ -3,10 +3,15 @@ package io.kestra.cli;
|
||||
import io.micronaut.core.annotation.Nullable;
|
||||
import io.micronaut.http.HttpHeaders;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.MediaType;
|
||||
import io.micronaut.http.MutableHttpRequest;
|
||||
import io.micronaut.http.body.ContextlessMessageBodyHandlerRegistry;
|
||||
import io.micronaut.http.body.MessageBodyHandlerRegistry;
|
||||
import io.micronaut.http.client.DefaultHttpClientConfiguration;
|
||||
import io.micronaut.http.client.HttpClientConfiguration;
|
||||
import io.micronaut.http.client.netty.DefaultHttpClient;
|
||||
import io.micronaut.http.netty.body.NettyJsonHandler;
|
||||
import io.micronaut.json.JsonMapper;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import picocli.CommandLine;
|
||||
@@ -39,7 +44,12 @@ public abstract class AbstractApiCommand extends AbstractCommand {
|
||||
private HttpClientConfiguration httpClientConfiguration;
|
||||
|
||||
protected DefaultHttpClient client() throws URISyntaxException {
|
||||
return new DefaultHttpClient(server.toURI(), httpClientConfiguration != null ? httpClientConfiguration : new DefaultHttpClientConfiguration());
|
||||
DefaultHttpClient defaultHttpClient = new DefaultHttpClient(server.toURI(), httpClientConfiguration != null ? httpClientConfiguration : new DefaultHttpClientConfiguration());
|
||||
MessageBodyHandlerRegistry defaultHandlerRegistry = defaultHttpClient.getHandlerRegistry();
|
||||
if (defaultHandlerRegistry instanceof ContextlessMessageBodyHandlerRegistry modifiableRegistry) {
|
||||
modifiableRegistry.add(MediaType.TEXT_JSON_TYPE, new NettyJsonHandler<>(JsonMapper.createDefault()));
|
||||
}
|
||||
return defaultHttpClient;
|
||||
}
|
||||
|
||||
protected <T> HttpRequest<T> requestOptions(MutableHttpRequest<T> request) {
|
||||
|
||||
@@ -19,7 +19,7 @@ class FlowValidateCommandTest {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
|
||||
try (ApplicationContext ctx = ApplicationContext.run(Environment.CLI, Environment.TEST)) {
|
||||
try (ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start()) {
|
||||
EmbeddedServer embeddedServer = ctx.getBean(EmbeddedServer.class);
|
||||
embeddedServer.start();
|
||||
|
||||
@@ -39,7 +39,7 @@ class FlowValidateCommandTest {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
System.setOut(new PrintStream(out));
|
||||
|
||||
try (ApplicationContext ctx = ApplicationContext.run(Environment.CLI, Environment.TEST)) {
|
||||
try (ApplicationContext ctx = ApplicationContext.builder().deduceEnvironment(false).start()) {
|
||||
EmbeddedServer embeddedServer = ctx.getBean(EmbeddedServer.class);
|
||||
embeddedServer.start();
|
||||
|
||||
|
||||
@@ -532,6 +532,10 @@ public class RunContext {
|
||||
this.initBean(applicationContext);
|
||||
this.initLogger(workerTrigger.getTriggerContext(), workerTrigger.getTrigger());
|
||||
|
||||
Map<String, Object> clone = new HashMap<>(this.variables);
|
||||
clone.put("addSecretConsumer", (Consumer<String>) s -> runContextLogger.usedSecret(s));
|
||||
this.variables = ImmutableMap.copyOf(clone);
|
||||
|
||||
// Mutability hack to update the triggerExecutionId for each evaluation on the worker
|
||||
return forScheduler(workerTrigger.getTriggerContext(), workerTrigger.getTrigger());
|
||||
}
|
||||
|
||||
@@ -99,10 +99,16 @@ public class VariableRenderer {
|
||||
Writer writer = new JsonWriter(new StringWriter());
|
||||
compiledTemplate.evaluate(writer, variables);
|
||||
result = writer.toString();
|
||||
} catch (IOException e) {
|
||||
throw new IllegalVariableEvaluationException(e);
|
||||
} catch (PebbleException e) {
|
||||
throw properPebbleException(e);
|
||||
} catch (IOException | PebbleException e) {
|
||||
String alternativeRender = this.alternativeRender(e, inline, variables);
|
||||
if (alternativeRender == null) {
|
||||
if (e instanceof PebbleException) {
|
||||
throw properPebbleException((PebbleException) e);
|
||||
}
|
||||
throw new IllegalVariableEvaluationException(e);
|
||||
} else {
|
||||
result = alternativeRender;
|
||||
}
|
||||
}
|
||||
|
||||
// post-process raw tags
|
||||
@@ -111,6 +117,18 @@ public class VariableRenderer {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method can be used in fallback for rendering an input string.
|
||||
*
|
||||
* @param e The exception that was throw by the default variable renderer.
|
||||
* @param inline The expression to be rendered.
|
||||
* @param variables The context variables.
|
||||
* @return The rendered string.
|
||||
*/
|
||||
protected String alternativeRender(Exception e, String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String putBackRawTags(Map<String, String> replacers, String result) {
|
||||
for (var entry : replacers.entrySet()) {
|
||||
result = result.replace(entry.getKey(), entry.getValue());
|
||||
|
||||
@@ -352,6 +352,11 @@ public abstract class AbstractScheduler implements Scheduler, Service {
|
||||
.conditionContext(flowWithTriggers.getConditionContext())
|
||||
.triggerContext(flowWithTriggers.TriggerContext.toBuilder().date(now()).stopAfter(flowWithTriggers.getAbstractTrigger().getStopAfter()).build())
|
||||
.build())
|
||||
.peek(f -> {
|
||||
if (f.getTriggerContext().getEvaluateRunningDate() != null || isExecutionNotRunning(f)) {
|
||||
this.triggerState.unlock(f.getTriggerContext());
|
||||
}
|
||||
})
|
||||
.filter(f -> f.getTriggerContext().getEvaluateRunningDate() == null)
|
||||
.filter(this::isExecutionNotRunning)
|
||||
.map(FlowWithPollingTriggerNextDate::of)
|
||||
|
||||
@@ -26,6 +26,13 @@ public interface SchedulerTriggerStateInterface {
|
||||
|
||||
List<Trigger> findByNextExecutionDateReadyForAllTenants(ZonedDateTime now, ScheduleContextInterface scheduleContext);
|
||||
|
||||
// Required for Kafka
|
||||
/**
|
||||
* Required for Kafka
|
||||
*/
|
||||
List<Trigger> findByNextExecutionDateReadyForGivenFlows(List<Flow> flows, ZonedDateTime now, ScheduleContextInterface scheduleContext);
|
||||
|
||||
/**
|
||||
* Required for Kafka
|
||||
*/
|
||||
void unlock(Trigger trigger);
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ import java.util.stream.StreamSupport;
|
||||
@Singleton
|
||||
@Slf4j
|
||||
public class FlowService {
|
||||
private final IllegalStateException NO_REPOSITORY_EXCEPTION = new IllegalStateException("No flow repository found. Make sure the `kestra.repository.type` property is set.");
|
||||
|
||||
@Inject
|
||||
RunContextFactory runContextFactory;
|
||||
|
||||
@@ -43,7 +45,7 @@ public class FlowService {
|
||||
ConditionService conditionService;
|
||||
|
||||
@Inject
|
||||
FlowRepositoryInterface flowRepository;
|
||||
Optional<FlowRepositoryInterface> flowRepository;
|
||||
|
||||
@Inject
|
||||
YamlFlowParser yamlFlowParser;
|
||||
@@ -62,6 +64,11 @@ public class FlowService {
|
||||
.tenantId(tenantId)
|
||||
.build();
|
||||
|
||||
if (flowRepository.isEmpty()) {
|
||||
throw NO_REPOSITORY_EXCEPTION;
|
||||
}
|
||||
|
||||
FlowRepositoryInterface flowRepository = this.flowRepository.get();
|
||||
return flowRepository
|
||||
.findById(withTenant.getTenantId(), withTenant.getNamespace(), withTenant.getId())
|
||||
.map(previous -> flowRepository.update(withTenant, previous, source, taskDefaultService.injectDefaults(withTenant)))
|
||||
@@ -69,7 +76,11 @@ public class FlowService {
|
||||
}
|
||||
|
||||
public List<FlowWithSource> findByNamespaceWithSource(String tenantId, String namespace) {
|
||||
return flowRepository.findByNamespaceWithSource(tenantId, namespace);
|
||||
if (flowRepository.isEmpty()) {
|
||||
throw NO_REPOSITORY_EXCEPTION;
|
||||
}
|
||||
|
||||
return flowRepository.get().findByNamespaceWithSource(tenantId, namespace);
|
||||
}
|
||||
|
||||
public Stream<Flow> keepLastVersion(Stream<Flow> stream) {
|
||||
|
||||
@@ -34,6 +34,7 @@ public class TaskDefaultService {
|
||||
|
||||
@Inject
|
||||
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
|
||||
@Nullable
|
||||
protected QueueInterface<LogEntry> logQueue;
|
||||
|
||||
/**
|
||||
|
||||
@@ -51,10 +51,15 @@ public abstract class AbstractTaskRunnerTest {
|
||||
var commands = initScriptCommands(runContext);
|
||||
Mockito.when(commands.getEnableOutputDirectory()).thenReturn(false);
|
||||
Mockito.when(commands.outputDirectoryEnabled()).thenReturn(false);
|
||||
Mockito.when(commands.getCommands()).thenReturn(ScriptService.scriptCommands(List.of("/bin/sh", "-c"), Collections.emptyList(), List.of("echo 'Hello World'")));
|
||||
|
||||
var taskRunner = taskRunner();
|
||||
assertThat(taskRunner.additionalVars(runContext, commands).containsKey(ScriptService.VAR_OUTPUT_DIR), is(false));
|
||||
assertThat(taskRunner.env(runContext, commands).containsKey(ScriptService.ENV_OUTPUT_DIR), is(false));
|
||||
|
||||
var result = taskRunner.run(runContext, commands, Collections.emptyList(), Collections.emptyList());
|
||||
assertThat(result, notNullValue());
|
||||
assertThat(result.getExitCode(), is(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -5,8 +5,6 @@ import io.kestra.core.models.executions.LogEntry;
|
||||
import io.kestra.core.models.flows.Flow;
|
||||
import io.kestra.core.queues.QueueFactoryInterface;
|
||||
import io.kestra.core.queues.QueueInterface;
|
||||
import io.kestra.core.tasks.log.Log;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -40,7 +38,6 @@ class RunContextLoggerTest {
|
||||
|
||||
Flow flow = TestsUtils.mockFlow();
|
||||
Execution execution = TestsUtils.mockExecution(flow, Map.of());
|
||||
Log log = Log.builder().id(IdUtils.create()).type(Log.class.getName()).build();
|
||||
|
||||
RunContextLogger runContextLogger = new RunContextLogger(
|
||||
logQueue,
|
||||
@@ -86,14 +83,13 @@ class RunContextLoggerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void secrets() throws InterruptedException {
|
||||
void secrets() {
|
||||
List<LogEntry> logs = new CopyOnWriteArrayList<>();
|
||||
List<LogEntry> matchingLog;
|
||||
logQueue.receive(either -> logs.add(either.getLeft()));
|
||||
|
||||
Flow flow = TestsUtils.mockFlow();
|
||||
Execution execution = TestsUtils.mockExecution(flow, Map.of());
|
||||
Log log = Log.builder().id(IdUtils.create()).type(Log.class.getName()).build();
|
||||
|
||||
RunContextLogger runContextLogger = new RunContextLogger(
|
||||
logQueue,
|
||||
|
||||
@@ -4,6 +4,8 @@ import io.kestra.core.encryption.EncryptionService;
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.kestra.core.metrics.MetricRegistry;
|
||||
import io.kestra.core.models.Label;
|
||||
import io.kestra.core.models.annotations.PluginProperty;
|
||||
import io.kestra.core.models.conditions.ConditionContext;
|
||||
import io.kestra.core.models.executions.Execution;
|
||||
import io.kestra.core.models.executions.LogEntry;
|
||||
import io.kestra.core.models.executions.TaskRun;
|
||||
@@ -14,16 +16,27 @@ import io.kestra.core.models.flows.State;
|
||||
import io.kestra.core.models.flows.Type;
|
||||
import io.kestra.core.models.flows.input.StringInput;
|
||||
import io.kestra.core.models.tasks.common.EncryptedString;
|
||||
import io.kestra.core.models.triggers.AbstractTrigger;
|
||||
import io.kestra.core.models.triggers.PollingTriggerInterface;
|
||||
import io.kestra.core.models.triggers.TriggerContext;
|
||||
import io.kestra.core.queues.QueueFactoryInterface;
|
||||
import io.kestra.core.queues.QueueInterface;
|
||||
import io.kestra.core.storages.StorageInterface;
|
||||
import io.kestra.core.tasks.test.PollingTrigger;
|
||||
import io.kestra.core.tasks.test.SleepTrigger;
|
||||
import io.kestra.core.utils.IdUtils;
|
||||
import io.kestra.core.utils.TestsUtils;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.context.annotation.Property;
|
||||
import io.micronaut.context.annotation.Value;
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Named;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.SuperBuilder;
|
||||
import org.exparity.hamcrest.date.ZonedDateTimeMatchers;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.event.Level;
|
||||
@@ -47,6 +60,9 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@Property(name = "kestra.tasks.tmp-dir.path", value = "/tmp/sub/dir/tmp/")
|
||||
class RunContextTest extends AbstractMemoryRunnerTest {
|
||||
@Inject
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
@Inject
|
||||
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
|
||||
QueueInterface<LogEntry> workerTaskLogQueue;
|
||||
@@ -66,6 +82,10 @@ class RunContextTest extends AbstractMemoryRunnerTest {
|
||||
@Value("${kestra.encryption.secret-key}")
|
||||
private String secretKey;
|
||||
|
||||
@Inject
|
||||
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
|
||||
private QueueInterface<LogEntry> logQueue;
|
||||
|
||||
@Test
|
||||
void logs() throws TimeoutException {
|
||||
List<LogEntry> logs = new CopyOnWriteArrayList<>();
|
||||
@@ -289,4 +309,57 @@ class RunContextTest extends AbstractMemoryRunnerTest {
|
||||
));
|
||||
assertThat(rendered.get("key"), is("value"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void secretTrigger() throws IllegalVariableEvaluationException {
|
||||
List<LogEntry> logs = new CopyOnWriteArrayList<>();
|
||||
List<LogEntry> matchingLog;
|
||||
logQueue.receive(either -> logs.add(either.getLeft()));
|
||||
|
||||
LogTrigger trigger = LogTrigger.builder()
|
||||
.type(SleepTrigger.class.getName())
|
||||
.id("unit-test")
|
||||
.format("john {{ secret('PASSWORD') }} doe")
|
||||
.build();
|
||||
|
||||
Map.Entry<ConditionContext, TriggerContext> mockedTrigger = TestsUtils.mockTrigger(runContextFactory, trigger);
|
||||
|
||||
WorkerTrigger workerTrigger = WorkerTrigger.builder()
|
||||
.trigger(trigger)
|
||||
.triggerContext(mockedTrigger.getValue())
|
||||
.conditionContext(mockedTrigger.getKey())
|
||||
.build();
|
||||
|
||||
RunContext runContext = mockedTrigger.getKey().getRunContext().forWorker(applicationContext, workerTrigger);
|
||||
Optional<Execution> evaluate = trigger.evaluate(mockedTrigger.getKey().withRunContext(runContext), mockedTrigger.getValue());
|
||||
|
||||
matchingLog = TestsUtils.awaitLogs(logs, 3);
|
||||
assertThat(matchingLog.stream().filter(logEntry -> logEntry.getLevel().equals(Level.INFO)).findFirst().orElse(null).getMessage(), is("john ******** doe"));
|
||||
}
|
||||
|
||||
|
||||
@SuperBuilder
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@Getter
|
||||
@NoArgsConstructor
|
||||
public static class LogTrigger extends AbstractTrigger implements PollingTriggerInterface {
|
||||
|
||||
@PluginProperty
|
||||
@NotNull
|
||||
private String format;
|
||||
|
||||
@Override
|
||||
public Optional<Execution> evaluate(ConditionContext conditionContext, TriggerContext context) throws IllegalVariableEvaluationException {
|
||||
conditionContext.getRunContext().logger().info(conditionContext.getRunContext().render(format));
|
||||
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Duration getInterval() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package io.kestra.core.runners;
|
||||
|
||||
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
|
||||
import io.micronaut.context.ApplicationContext;
|
||||
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
|
||||
import jakarta.inject.Inject;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@MicronautTest
|
||||
class VariableRendererTest {
|
||||
|
||||
@Inject
|
||||
ApplicationContext applicationContext;
|
||||
|
||||
@Inject
|
||||
VariableRenderer.VariableConfiguration variableConfiguration;
|
||||
|
||||
@Test
|
||||
void shouldRenderUsingAlternativeRendering() throws IllegalVariableEvaluationException {
|
||||
TestVariableRenderer renderer = new TestVariableRenderer(applicationContext, variableConfiguration);
|
||||
String render = renderer.render("{{ dummy }}", Map.of());
|
||||
Assertions.assertEquals("result", render);
|
||||
}
|
||||
|
||||
|
||||
public static class TestVariableRenderer extends VariableRenderer {
|
||||
|
||||
public TestVariableRenderer(ApplicationContext applicationContext,
|
||||
VariableConfiguration variableConfiguration) {
|
||||
super(applicationContext, variableConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String alternativeRender(Exception e, String inline, Map<String, Object> variables) throws IllegalVariableEvaluationException {
|
||||
return "result";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
version=0.16.0
|
||||
version=0.16.2
|
||||
|
||||
jacksonVersion=2.16.2
|
||||
micronautVersion=4.3.7
|
||||
# Cannot upgrade for now due to https://github.com/kestra-io/kestra-ee/issues/1085
|
||||
micronautVersion=4.3.4
|
||||
lombokVersion=1.18.32
|
||||
slf4jVersion=2.0.12
|
||||
|
||||
|
||||
@@ -84,4 +84,7 @@ public class JdbcSchedulerTriggerState implements SchedulerTriggerStateInterface
|
||||
public List<Trigger> findByNextExecutionDateReadyForGivenFlows(List<Flow> flows, ZonedDateTime now, ScheduleContextInterface scheduleContext) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock(Trigger trigger) {}
|
||||
}
|
||||
|
||||
@@ -79,4 +79,7 @@ public class MemorySchedulerTriggerState implements SchedulerTriggerStateInterfa
|
||||
public List<Trigger> findByNextExecutionDateReadyForGivenFlows(List<Flow> flows, ZonedDateTime now, ScheduleContextInterface scheduleContext) {
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock(Trigger trigger) {}
|
||||
}
|
||||
|
||||
8
ui/package-lock.json
generated
8
ui/package-lock.json
generated
@@ -8,7 +8,7 @@
|
||||
"name": "kestra",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@kestra-io/ui-libs": "^0.0.42",
|
||||
"@kestra-io/ui-libs": "^0.0.43",
|
||||
"@vue-flow/background": "^1.3.0",
|
||||
"@vue-flow/controls": "^1.1.1",
|
||||
"@vue-flow/core": "^1.33.5",
|
||||
@@ -704,9 +704,9 @@
|
||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||
},
|
||||
"node_modules/@kestra-io/ui-libs": {
|
||||
"version": "0.0.42",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.42.tgz",
|
||||
"integrity": "sha512-3Uti8oK94KDNcxxxpODdWk7Ed1BUzGNdKOrJXMt0IfYqrCLJ3bh11C92oBJyS4LN5PHxZjLsAFay/akiGNavtA==",
|
||||
"version": "0.0.43",
|
||||
"resolved": "https://registry.npmjs.org/@kestra-io/ui-libs/-/ui-libs-0.0.43.tgz",
|
||||
"integrity": "sha512-tDGJD+yTn3L5e+c64hJBYb3qOzpw0y3OZML8nXb3trZ5/q0s1TkruY1twu5MrJxhZzNXUr0EA/VlxEQZ2ZKVCQ==",
|
||||
"peerDependencies": {
|
||||
"@vue-flow/background": "^1.3.0",
|
||||
"@vue-flow/controls": "^1.1.1",
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@kestra-io/ui-libs": "^0.0.42",
|
||||
"@kestra-io/ui-libs": "^0.0.43",
|
||||
"@vue-flow/background": "^1.3.0",
|
||||
"@vue-flow/controls": "^1.1.1",
|
||||
"@vue-flow/core": "^1.33.5",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</th>
|
||||
<td :colspan="dates.length">
|
||||
<td :colspan="dates.length" @click="onTaskSelect(serie.task)" class="cursor-pointer">
|
||||
<el-tooltip placement="top" :persistent="false" transition="" :hide-after="0">
|
||||
<template #content>
|
||||
<span style="white-space: pre-wrap;">
|
||||
@@ -35,7 +35,7 @@
|
||||
<div
|
||||
:style="{left: serie.start + '%', width: serie.width + '%'}"
|
||||
class="task-progress"
|
||||
@click="onTaskSelect(serie.task)"
|
||||
@click="onTaskSelect(serie.id)"
|
||||
>
|
||||
<div class="progress">
|
||||
<div
|
||||
@@ -58,7 +58,7 @@
|
||||
@follow="forwardEvent('follow', $event)"
|
||||
:target-execution="execution"
|
||||
:target-flow="flow"
|
||||
:show-logs="taskTypeByTaskRunId[serie.task.id] !== 'io.kestra.core.tasks.flows.ForEachItem'"
|
||||
:show-logs="taskTypeByTaskRunId[serie.id] !== 'io.kestra.core.tasks.flows.ForEachItem'"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -294,13 +294,13 @@
|
||||
}
|
||||
this.dates = dates;
|
||||
},
|
||||
onTaskSelect(taskRun) {
|
||||
if(this.selectedTaskRuns.includes(taskRun.id)) {
|
||||
this.selectedTaskRuns = this.selectedTaskRuns.filter(id => id !== taskRun.id);
|
||||
onTaskSelect(taskRunId) {
|
||||
if(this.selectedTaskRuns.includes(taskRunId)) {
|
||||
this.selectedTaskRuns = this.selectedTaskRuns.filter(id => id !== taskRunId);
|
||||
return
|
||||
}
|
||||
|
||||
this.selectedTaskRuns.push(taskRun.id);
|
||||
this.selectedTaskRuns.push(taskRunId);
|
||||
},
|
||||
stopRealTime() {
|
||||
this.realTime = false
|
||||
@@ -323,6 +323,10 @@
|
||||
|
||||
}
|
||||
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
table {
|
||||
table-layout: fixed;
|
||||
width: 100%;
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
<h5>{{ $t("revision") + `: ` + revision }}</h5>
|
||||
</template>
|
||||
|
||||
<editor v-model="revisionYaml" lang="yaml" />
|
||||
<editor v-model="revisionYaml" lang="yaml" :full-height="false" :input="true" :navbar="false" :read-only="true" />
|
||||
</drawer>
|
||||
</div>
|
||||
<div v-else>
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
BookMultipleOutline: shallowRef(BookMultipleOutline),
|
||||
Close: shallowRef(Close)
|
||||
},
|
||||
oldDecorations: [],
|
||||
editorDocumentation: undefined,
|
||||
plugin: undefined,
|
||||
taskType: undefined,
|
||||
@@ -209,6 +208,8 @@
|
||||
|
||||
this.editor = editor;
|
||||
|
||||
this.decorations = this.editor.createDecorationsCollection();
|
||||
|
||||
if (!this.original) {
|
||||
this.editor.onDidBlurEditorWidget(() => {
|
||||
this.$emit("focusout", editor.getValue());
|
||||
@@ -308,27 +309,27 @@
|
||||
this.editor.onDidContentSizeChange(_ => {
|
||||
if (this.guidedProperties.monacoRange) {
|
||||
editor.revealLine(this.guidedProperties.monacoRange.endLineNumber);
|
||||
let decorations = [
|
||||
{
|
||||
range: this.guidedProperties.monacoRange,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
inlineClassName: "highlight-text"
|
||||
},
|
||||
className: "highlight-text",
|
||||
}
|
||||
];
|
||||
decorations = this.guidedProperties.monacoDisableRange ? decorations.concat([
|
||||
{
|
||||
const decorationsToAdd = [];
|
||||
decorationsToAdd.push({
|
||||
range: this.guidedProperties.monacoRange,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
inlineClassName: "highlight-text"
|
||||
},
|
||||
className: "highlight-text",
|
||||
});
|
||||
if (this.guidedProperties.monacoDisableRange) {
|
||||
decorationsToAdd.push({
|
||||
range: this.guidedProperties.monacoDisableRange,
|
||||
options: {
|
||||
isWholeLine: true,
|
||||
inlineClassName: "disable-text"
|
||||
},
|
||||
className: "disable-text",
|
||||
},
|
||||
]) : decorations;
|
||||
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, decorations)
|
||||
});
|
||||
}
|
||||
|
||||
this.decorations.set(decorationsToAdd);
|
||||
} else {
|
||||
this.highlightPebble();
|
||||
}
|
||||
@@ -363,14 +364,14 @@
|
||||
highlightPebble() {
|
||||
// Highlight code that match pebble content
|
||||
let model = this.editor.getModel();
|
||||
let decorations = [];
|
||||
let text = model.getValue();
|
||||
let regex = new RegExp("\\{\\{(.+?)}}", "g");
|
||||
let match;
|
||||
const decorationsToAdd = [];
|
||||
while ((match = regex.exec(text)) !== null) {
|
||||
let startPos = model.getPositionAt(match.index);
|
||||
let endPos = model.getPositionAt(match.index + match[0].length);
|
||||
decorations.push({
|
||||
decorationsToAdd.push({
|
||||
range: {
|
||||
startLineNumber: startPos.lineNumber,
|
||||
startColumn: startPos.column,
|
||||
@@ -382,7 +383,7 @@
|
||||
}
|
||||
});
|
||||
}
|
||||
this.oldDecorations = this.editor.deltaDecorations(this.oldDecorations, decorations);
|
||||
this.decorations.set(decorationsToAdd);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -809,7 +809,7 @@
|
||||
ref="editorDomElement"
|
||||
v-if="combinedEditor || viewType === editorViewTypes.SOURCE"
|
||||
:class="combinedEditor ? 'editor-combined' : ''"
|
||||
:style="combinedEditor ? {'flex-basis': leftEditorWidth, 'flex-grow': 0} : {}"
|
||||
:style="combinedEditor ? {'flex': '0 0 ' + leftEditorWidth} : {}"
|
||||
@save="save"
|
||||
@execute="execute"
|
||||
v-model="flowYaml"
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
import TimerCogOutline from "vue-material-design-icons/TimerCogOutline.vue";
|
||||
import {mapState} from "vuex";
|
||||
import ChartBoxOutline from "vue-material-design-icons/ChartBoxOutline.vue";
|
||||
import Connection from "vue-material-design-icons/Connection.vue";
|
||||
import {shallowRef} from "vue";
|
||||
|
||||
export default {
|
||||
@@ -169,6 +170,15 @@
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
{
|
||||
href: {name: "plugins/list"},
|
||||
routes: this.routeStartWith("plugins"),
|
||||
title: this.$t("plugins.names"),
|
||||
icon: {
|
||||
element: shallowRef(Connection),
|
||||
class: "menu-icon"
|
||||
},
|
||||
},
|
||||
{
|
||||
title: this.$t("administration"),
|
||||
routes: this.routeStartWith("admin"),
|
||||
|
||||
@@ -4,7 +4,6 @@ import io.micronaut.http.HttpStatus;
|
||||
import io.micronaut.http.exceptions.HttpStatusException;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -15,7 +14,7 @@ public class RequestUtils {
|
||||
.stream()
|
||||
.map(s -> {
|
||||
String[] split = s.split("[: ]+");
|
||||
if (split.length < 2) {
|
||||
if (split.length < 2 || split[0] == null || split[0].isEmpty()) {
|
||||
throw new HttpStatusException(HttpStatus.UNPROCESSABLE_ENTITY, "Invalid queryString parameter");
|
||||
}
|
||||
|
||||
|
||||
@@ -24,10 +24,7 @@ import io.kestra.webserver.responses.BulkResponse;
|
||||
import io.kestra.webserver.responses.PagedResults;
|
||||
import io.micronaut.core.type.Argument;
|
||||
import io.micronaut.data.model.Pageable;
|
||||
import io.micronaut.http.HttpRequest;
|
||||
import io.micronaut.http.HttpResponse;
|
||||
import io.micronaut.http.HttpStatus;
|
||||
import io.micronaut.http.MediaType;
|
||||
import io.micronaut.http.*;
|
||||
import io.micronaut.http.client.annotation.Client;
|
||||
import io.micronaut.http.client.exceptions.HttpClientResponseException;
|
||||
import io.micronaut.http.client.multipart.MultipartBody;
|
||||
@@ -1086,4 +1083,21 @@ class ExecutionControllerTest extends JdbcH2ControllerTest {
|
||||
|
||||
assertThat(response.getCount(), is(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nullLabels() {
|
||||
MultipartBody requestBody = createInputsFlowBody();
|
||||
|
||||
// null keys are forbidden
|
||||
MutableHttpRequest<MultipartBody> requestNullKey = HttpRequest
|
||||
.POST("/api/v1/executions/" + TESTS_FLOW_NS + "/inputs?labels=:value", requestBody)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE);
|
||||
assertThrows(HttpClientResponseException.class, () -> client.toBlocking().retrieve(requestNullKey, Execution.class));
|
||||
|
||||
// null values are forbidden
|
||||
MutableHttpRequest<MultipartBody> requestNullValue = HttpRequest
|
||||
.POST("/api/v1/executions/" + TESTS_FLOW_NS + "/inputs?labels=key:", requestBody)
|
||||
.contentType(MediaType.MULTIPART_FORM_DATA_TYPE);
|
||||
assertThrows(HttpClientResponseException.class, () -> client.toBlocking().retrieve(requestNullValue, Execution.class));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user