Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.kestra.plugin.scripts.deno;

import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

Expand Down Expand Up @@ -31,7 +32,7 @@
@NoArgsConstructor
@Schema(
title = "Run inline Deno script",
description = "Executes a multi-line Deno script inside the default 'denoland/deno' image unless overridden. Script is written to a temp .ts file and run with 'deno run'; add required --allow-* flags in beforeCommands or interpreter options."
description = "Executes a multi-line Deno script inside the default 'denoland/deno' image unless overridden. Script is written to a temp .ts file and run with 'deno run'; the `permissions` property controls which --allow-* flags are granted."
)
@Plugin(
examples = {
Expand All @@ -48,6 +49,25 @@
console.log("Hello from kestra!");
"""
),
@Example(
title = "Read an environment variable and write an output file from an inline Deno script.",
full = true,
code = """
id: deno_env_and_output_file
namespace: company.team
tasks:
- id: deno_script
type: io.kestra.plugin.scripts.deno.Script
env:
MY_VAR: hello
outputFiles:
- out.txt
script: |
const value = Deno.env.get("MY_VAR");
console.log("ENV=" + value);
Deno.writeTextFileSync("out.txt", value ?? "");
"""
),
}
)
public class Script extends AbstractExecScript implements RunnableTask<ScriptOutput> {
Expand All @@ -69,6 +89,18 @@ public class Script extends AbstractExecScript implements RunnableTask<ScriptOut
@PluginProperty(language = MonacoLanguages.TYPESCRIPT, group = "main")
protected Property<String> script;

@Schema(
title = "Deno permission flags",
description = """
Flags passed to `deno run` to grant the script access to the outside world, e.g. `--allow-net`, `--allow-all`.
Defaults to `--allow-env`, `--allow-read` and `--allow-write` so that the `env`, `inputFiles` and `outputFiles` \
properties work the same way as on every other script task. Set to an empty list to run under Deno's \
secure-by-default sandbox with no permissions at all."""
)
@Builder.Default
@PluginProperty(group = "execution")
protected Property<List<String>> permissions = Property.ofValue(List.of("--allow-env", "--allow-read", "--allow-write"));

@Override
protected DockerOptions injectDefaults(RunContext runContext, DockerOptions original) throws IllegalVariableEvaluationException {
var builder = original.toBuilder();
Expand All @@ -92,15 +124,18 @@ public ScriptOutput run(RunContext runContext) throws Exception {

TargetOS os = runContext.render(this.targetOS).as(TargetOS.class).orElse(null);

List<String> rPermissions = runContext.render(this.permissions).asList(String.class);
List<String> denoCommand = new ArrayList<>(List.of("deno", "run"));
denoCommand.addAll(rPermissions);
denoCommand.add(commands.getTaskRunner().toAbsolutePath(runContext, commands, relativeScriptPath.toString(), os));

return commands
.withInterpreter(this.interpreter)
.withBeforeCommands(beforeCommands)
.withBeforeCommandsWithOptions(true)
.withCommands(
Property.ofValue(
List.of(
String.join(" ", "deno", "run", commands.getTaskRunner().toAbsolutePath(runContext, commands, relativeScriptPath.toString(), os))
)
List.of(String.join(" ", denoCommand))
)
)
.withTargetOS(os)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ Run TypeScript and JavaScript with Deno's secure-by-default runtime — imports
`Script` runs inline TypeScript or JavaScript defined in the `script` property — best for short, flow-specific logic. `Commands` runs Deno commands (e.g., `deno run --allow-net main.ts`) against script files; use it when your code lives in [namespace files](https://kestra.io/docs/concepts/namespace-files) shared across flows, or is cloned from a Git repository with a preceding `Clone` task.

Deno resolves imports from URLs at runtime — no install step needed for URL-based imports. For local module graphs, include a `deno.json` import map via namespace files. Pass Deno permission flags (`--allow-net`, `--allow-read`, `--allow-env`, etc.) directly in `commands` to scope what the script can access.

`Script` defaults its `permissions` property to `--allow-env`, `--allow-read` and `--allow-write` so that `env`, `inputFiles` and `outputFiles` work out of the box like on every other script task. Override `permissions` to add flags (e.g. `--allow-net`) or set it to an empty list to run under Deno's secure-by-default sandbox.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.kestra.plugins.scripts.deno;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArrayList;

Expand Down Expand Up @@ -57,4 +58,32 @@ void script() throws Exception {
receive.blockLast();
assertThat(List.copyOf(logs).stream().anyMatch(log -> log.getMessage() != null && log.getMessage().contains(expectedLog)), is(true));
}

@Test
void envInputAndOutputFiles() throws Exception {
List<LogEntry> logs = new CopyOnWriteArrayList<>();
Flux<LogEntry> receive = TestsUtils.receive(logQueue, l -> logs.add(l.getLeft()));

Script script = Script.builder()
.id("deno-script-" + UUID.randomUUID())
.type(Script.class.getName())
.env(Property.ofValue(Map.of("MY_VAR", "hello")))
.inputFiles(Map.of("in.txt", "world"))
.outputFiles(Property.ofValue(List.of("out.txt")))
.script(Property.ofValue("""
console.log("ENV=" + Deno.env.get("MY_VAR"));
console.log("INPUT=" + Deno.readTextFileSync("in.txt").trim());
Deno.writeTextFileSync("out.txt", "output content");
"""))
.build();

RunContext runContext = TestsUtils.mockRunContext(runContextFactory, script, ImmutableMap.of());
ScriptOutput run = script.run(runContext);

assertThat(run.getExitCode(), is(0));
receive.blockLast();
assertThat(List.copyOf(logs).stream().anyMatch(log -> log.getMessage() != null && log.getMessage().contains("ENV=hello")), is(true));
assertThat(List.copyOf(logs).stream().anyMatch(log -> log.getMessage() != null && log.getMessage().contains("INPUT=world")), is(true));
assertThat(run.getOutputFiles().get("out.txt").toString(), org.hamcrest.Matchers.startsWith("kestra://"));
}
}
Loading