Skip to content

Commit 72254c2

Browse files
feat(scripts): add support for Lua Scripts and Commands tasks (#262)
1 parent 608ff06 commit 72254c2

File tree

16 files changed

+466
-0
lines changed

16 files changed

+466
-0
lines changed

plugin-script-lua/build.gradle

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
project.description = 'Execute Lua scripts as part of Kestra data workflows.'
2+
3+
jar {
4+
manifest {
5+
attributes(
6+
"X-Kestra-Name": project.name,
7+
"X-Kestra-Title": "Lua",
8+
"X-Kestra-Group": project.group + ".scripts.lua",
9+
"X-Kestra-Description": project.description,
10+
"X-Kestra-Version": project.version
11+
)
12+
}
13+
}
14+
15+
dependencies {
16+
implementation project(':plugin-script')
17+
18+
testImplementation project(':plugin-script').sourceSets.test.output
19+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package io.kestra.plugin.scripts.lua;
2+
3+
import io.kestra.core.models.annotations.Example;
4+
import io.kestra.core.models.annotations.Plugin;
5+
import io.kestra.core.models.property.Property;
6+
import io.kestra.core.models.tasks.runners.TargetOS;
7+
import io.kestra.core.runners.RunContext;
8+
import io.kestra.plugin.scripts.exec.AbstractExecScript;
9+
import io.kestra.plugin.scripts.exec.scripts.models.ScriptOutput;
10+
import io.swagger.v3.oas.annotations.media.Schema;
11+
import lombok.*;
12+
import lombok.experimental.SuperBuilder;
13+
import java.util.List;
14+
15+
@SuperBuilder
16+
@ToString
17+
@EqualsAndHashCode
18+
@Getter
19+
@NoArgsConstructor
20+
@Schema(title = "Execute Lua commands from the CLI.")
21+
@Plugin(examples = {
22+
@Example(
23+
full = true,
24+
title = "Execute one or multiple Lua commands.",
25+
code = """
26+
id: lua_commands
27+
namespace: company.team
28+
tasks:
29+
- id: lua
30+
type: io.kestra.plugin.scripts.lua.Commands
31+
commands:
32+
- lua -e 'print("Hello from kestra!")'
33+
"""
34+
)
35+
})
36+
public class Commands extends AbstractExecScript {
37+
private static final String DEFAULT_IMAGE = "nickblah/lua";
38+
39+
@Builder.Default
40+
protected Property<String> containerImage = Property.ofValue(DEFAULT_IMAGE);
41+
42+
@Schema(title = "The commands to run.")
43+
protected Property<List<String>> commands;
44+
45+
@Override
46+
public ScriptOutput run(RunContext runContext) throws Exception {
47+
TargetOS os = runContext.render(this.targetOS).as(TargetOS.class).orElse(null);
48+
49+
return this.commands(runContext)
50+
.withInterpreter(this.interpreter)
51+
.withBeforeCommands(beforeCommands)
52+
.withBeforeCommandsWithOptions(true)
53+
.withCommands(commands)
54+
.withTargetOS(os)
55+
.run();
56+
}
57+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package io.kestra.plugin.scripts.lua;
2+
3+
import io.kestra.core.exceptions.IllegalVariableEvaluationException;
4+
import io.kestra.core.models.annotations.Example;
5+
import io.kestra.core.models.annotations.Plugin;
6+
import io.kestra.core.models.property.Property;
7+
import io.kestra.core.models.tasks.runners.TargetOS;
8+
import io.kestra.core.runners.FilesService;
9+
import io.kestra.core.runners.RunContext;
10+
import io.kestra.plugin.scripts.exec.AbstractExecScript;
11+
import io.kestra.plugin.scripts.exec.scripts.models.DockerOptions;
12+
import io.kestra.plugin.scripts.exec.scripts.models.ScriptOutput;
13+
import io.kestra.plugin.scripts.exec.scripts.runners.CommandsWrapper;
14+
import io.swagger.v3.oas.annotations.media.Schema;
15+
import jakarta.validation.constraints.NotNull;
16+
import lombok.*;
17+
import lombok.experimental.SuperBuilder;
18+
19+
import java.nio.file.Path;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
@SuperBuilder
24+
@ToString
25+
@EqualsAndHashCode
26+
@Getter
27+
@NoArgsConstructor
28+
@Schema(title = "Execute a Lua script.")
29+
@Plugin(examples = {
30+
@Example(
31+
title = "Run a simple inline Lua script.",
32+
full = true,
33+
code = """
34+
id: lua_inline
35+
namespace: company.team
36+
tasks:
37+
- id: lua_script
38+
type: io.kestra.plugin.scripts.lua.Script
39+
script: |
40+
local message = "Hello from Kestra!"
41+
print(message)
42+
"""
43+
),
44+
})
45+
public class Script extends AbstractExecScript {
46+
private static final String DEFAULT_IMAGE = "nickblah/lua";
47+
48+
@Builder.Default
49+
protected Property<String> containerImage = Property.ofValue(DEFAULT_IMAGE);
50+
51+
@Schema(title = "The inline script content.")
52+
@NotNull
53+
protected Property<String> script;
54+
55+
@Override
56+
protected DockerOptions injectDefaults(RunContext runContext, DockerOptions original) throws IllegalVariableEvaluationException {
57+
var builder = original.toBuilder();
58+
if (original.getImage() == null) {
59+
builder.image(runContext.render(this.getContainerImage()).as(String.class).orElse(null));
60+
}
61+
return builder.build();
62+
}
63+
64+
@Override
65+
public ScriptOutput run(RunContext runContext) throws Exception {
66+
CommandsWrapper commands = this.commands(runContext);
67+
68+
Map<String, String> inputFiles = FilesService.inputFiles(runContext, commands.getTaskRunner().additionalVars(runContext, commands), this.getInputFiles());
69+
Path relativeScriptPath = runContext.workingDir().path().relativize(runContext.workingDir().createTempFile(".lua"));
70+
inputFiles.put(
71+
relativeScriptPath.toString(),
72+
commands.render(runContext, this.script)
73+
);
74+
commands = commands.withInputFiles(inputFiles);
75+
76+
TargetOS os = runContext.render(this.targetOS).as(TargetOS.class).orElse(null);
77+
78+
return commands
79+
.withInterpreter(this.interpreter)
80+
.withBeforeCommands(beforeCommands)
81+
.withBeforeCommandsWithOptions(true)
82+
.withCommands(Property.ofValue(List.of(
83+
String.join(" ", "lua", commands.getTaskRunner().toAbsolutePath(runContext, commands, relativeScriptPath.toString(), os))
84+
)))
85+
.withTargetOS(os)
86+
.run();
87+
}
88+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@PluginSubGroup(
2+
description = "This sub-group of plugins contains tasks for running Lua script.",
3+
categories = { PluginSubGroup.PluginCategory.SCRIPT }
4+
)
5+
package io.kestra.plugin.scripts.lua;
6+
7+
import io.kestra.core.models.annotations.PluginSubGroup;
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 27 additions & 0 deletions
Loading
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.kestra.plugins.scripts.lua;
2+
3+
import com.google.common.collect.ImmutableMap;
4+
import io.kestra.core.junit.annotations.KestraTest;
5+
import io.kestra.core.models.executions.LogEntry;
6+
import io.kestra.core.models.property.Property;
7+
import io.kestra.core.queues.QueueFactoryInterface;
8+
import io.kestra.core.queues.QueueInterface;
9+
import io.kestra.core.runners.RunContext;
10+
import io.kestra.core.runners.RunContextFactory;
11+
import io.kestra.core.utils.IdUtils;
12+
import io.kestra.core.utils.TestsUtils;
13+
import io.kestra.plugin.scripts.exec.scripts.models.ScriptOutput;
14+
import io.kestra.plugin.scripts.lua.Commands;
15+
import jakarta.inject.Inject;
16+
import jakarta.inject.Named;
17+
import org.junit.jupiter.api.Test;
18+
import reactor.core.publisher.Flux;
19+
20+
import java.util.ArrayList;
21+
import java.util.List;
22+
import java.util.Map;
23+
24+
import static org.hamcrest.MatcherAssert.assertThat;
25+
import static org.hamcrest.Matchers.is;
26+
27+
@KestraTest
28+
public class CommandsTest {
29+
@Inject
30+
RunContextFactory runContextFactory;
31+
32+
@Inject
33+
@Named(QueueFactoryInterface.WORKERTASKLOG_NAMED)
34+
private QueueInterface<LogEntry> logQueue;
35+
36+
@Test
37+
void task() throws Exception {
38+
List<LogEntry> logs = new ArrayList<>();
39+
Flux<LogEntry> receive = TestsUtils.receive(logQueue, l -> logs.add(l.getLeft()));
40+
41+
Commands luaCommands = Commands.builder()
42+
.id(IdUtils.create())
43+
.type(Commands.class.getName())
44+
.commands(Property.ofValue(List.of("lua main.lua")))
45+
.inputFiles(Map.of(
46+
"main.lua", "print(\"Hello from kestra!\");"
47+
))
48+
.build();
49+
50+
RunContext runContext = TestsUtils.mockRunContext(runContextFactory, luaCommands, ImmutableMap.of());
51+
ScriptOutput run = luaCommands.run(runContext);
52+
53+
assertThat(run.getExitCode(), is(0));
54+
55+
String expectedLog = "Hello from kestra!";
56+
TestsUtils.awaitLog(logs, log -> log.getMessage() != null && log.getMessage().contains(expectedLog));
57+
receive.blockLast();
58+
assertThat(logs.stream().anyMatch(log -> log.getMessage() != null && log.getMessage().contains(expectedLog)), is(true));
59+
}
60+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.kestra.plugins.scripts.lua;
2+
3+
import io.kestra.core.junit.annotations.ExecuteFlow;
4+
import io.kestra.core.junit.annotations.KestraTest;
5+
import io.kestra.core.models.executions.Execution;
6+
import io.kestra.core.models.flows.State;
7+
import org.junit.jupiter.api.Test;
8+
9+
import static org.hamcrest.MatcherAssert.assertThat;
10+
import static org.hamcrest.Matchers.hasSize;
11+
import static org.hamcrest.Matchers.is;
12+
13+
@KestraTest(startRunner = true)
14+
class RunnerTest {
15+
@Test
16+
@ExecuteFlow("sanity-checks/all_lua.yaml")
17+
void all_lua(Execution execution) {
18+
assertThat(execution.getTaskRunList(), hasSize(2));
19+
assertThat(execution.getState().getCurrent(), is(State.Type.SUCCESS));
20+
}
21+
}

0 commit comments

Comments
 (0)