diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java index 116da6238..d125572cd 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/WorkflowApplication.java @@ -70,6 +70,7 @@ import java.util.Optional; import java.util.ServiceLoader; import java.util.ServiceLoader.Provider; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -112,6 +113,7 @@ public class WorkflowApplication implements AutoCloseable { private final AllStrategyCorrelationInfoFactory allStrategyCorrelationInfoFactory; private final WorkflowLifeCycleCloudEventFactory lifeCycleCloudEventFactory; private final ScheduledExecutorService schedulerExecutorService; + private final Set allowedCommands; private WorkflowApplication(Builder builder) { this.taskFactory = builder.taskFactory; @@ -147,6 +149,7 @@ private WorkflowApplication(Builder builder) { this.allStrategyCorrelationInfoFactory = builder.allStrategyCorrelationInfoFactory; this.lifeCycleCloudEventFactory = builder.lifeCycleCloudEventFactory; this.schedulerExecutorService = builder.schedulerExecutorService; + this.allowedCommands = Collections.unmodifiableSet(builder.allowedCommands); } public TaskExecutorFactory taskFactory() { @@ -274,6 +277,7 @@ public SchemaValidator getValidator(SchemaInline inline) { private WorkflowLifeCycleCloudEventFactory lifeCycleCloudEventFactory; private CronResolverFactory cronResolverFactory; private ScheduledExecutorService schedulerExecutorService; + private Set allowedCommands = new HashSet<>(); private Builder() { ServiceLoader.load(NamedWorkflowAdditionalObject.class) @@ -396,6 +400,16 @@ public Builder withModelFactory(WorkflowModelFactory modelFactory) { return this; } + public Builder withAllowedCommand(String command) { + this.allowedCommands.add(command); + return this; + } + + public Builder withAllowedCommands(Collection commands) { + this.allowedCommands.addAll(commands); + return this; + } + public Builder withContextFactory(WorkflowModelFactory contextFactory) { this.contextFactory = contextFactory; return this; @@ -690,4 +704,8 @@ public AllStrategyCorrelationInfoFactory allStrategyCorrelationInfoFactory() { public WorkflowLifeCycleCloudEventFactory lifeCycleCloudEventFactory() { return lifeCycleCloudEventFactory; } + + public Set allowedCommands() { + return allowedCommands; + } } diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java index 349c4a2a8..a5d1d3bc3 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java @@ -23,23 +23,23 @@ import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.WorkflowValueResolver; import io.serverlessworkflow.impl.scripts.ScriptUtils; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.CompletableFuture; public class RunShellExecutor implements CallableTask { private final WorkflowValueResolver shellCommand; - private final Map, Optional>> - shellArguments; + private final List> shellArguments; private final Optional>> shellEnv; private final Optional returnType; public RunShellExecutor( WorkflowValueResolver shellCommand, - Map, Optional>> shellArguments, + List> shellArguments, Optional>> shellEnv, Optional returnType) { - super(); this.shellCommand = shellCommand; this.shellArguments = shellArguments; this.shellEnv = shellEnv; @@ -49,16 +49,21 @@ public RunShellExecutor( @Override public CompletableFuture apply( WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel model) { - StringBuilder commandBuilder = - new StringBuilder(shellCommand.apply(workflowContext, taskContext, model)); - for (var entry : shellArguments.entrySet()) { - commandBuilder.append(" ").append(entry.getKey().apply(workflowContext, taskContext, model)); - entry - .getValue() - .ifPresent( - v -> commandBuilder.append("=").append(v.apply(workflowContext, taskContext, model))); + + String command = shellCommand.apply(workflowContext, taskContext, model); + if (!workflowContext.definition().application().allowedCommands().contains(command)) { + return CompletableFuture.failedFuture( + new SecurityException( + "Command " + + command + + " is not allowed. Please verify the set of allowed commands passed to the application")); } - ProcessBuilder builder = new ProcessBuilder("sh", "-c", commandBuilder.toString()); + + List commandAndArgs = new ArrayList<>(); + commandAndArgs.add(command); + shellArguments.forEach(f -> commandAndArgs.add(f.apply(workflowContext, taskContext, model))); + + ProcessBuilder builder = new ProcessBuilder(commandAndArgs); shellEnv.ifPresent( map -> ScriptUtils.addEnviromment(builder, map.apply(workflowContext, taskContext, model))); diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutorBuilder.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutorBuilder.java index ce31cefab..2143a5890 100644 --- a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutorBuilder.java +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutorBuilder.java @@ -20,10 +20,8 @@ import io.serverlessworkflow.api.types.Shell; import io.serverlessworkflow.impl.WorkflowDefinition; import io.serverlessworkflow.impl.WorkflowUtils; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; public class RunShellExecutorBuilder implements RunnableTaskBuilder { @@ -37,19 +35,10 @@ public CallableTask build(RunShell taskConfiguration, WorkflowDefinition definit WorkflowUtils.buildStringFilter( definition.application(), taskConfiguration.getShell().getCommand()), shell.getArguments() != null - ? shell.getArguments().getAdditionalProperties().entrySet().stream() - .collect( - Collectors.toMap( - e -> WorkflowUtils.buildStringFilter(definition.application(), e.getKey()), - e -> - e.getValue() != null - ? Optional.of( - WorkflowUtils.buildStringFilter( - definition.application(), e.getValue().toString())) - : Optional.empty(), - (x, y) -> y, - LinkedHashMap::new)) - : Map.of(), + ? shell.getArguments().stream() + .map(s -> WorkflowUtils.buildStringFilter(definition.application(), s)) + .toList() + : List.of(), shell.getEnvironment() != null ? Optional.of( WorkflowUtils.buildMapResolver( diff --git a/impl/test/src/test/java/io/serverlessworkflow/impl/test/RunShellExecutorTest.java b/impl/test/src/test/java/io/serverlessworkflow/impl/test/RunShellExecutorTest.java index 073a0bf6c..2435365ca 100644 --- a/impl/test/src/test/java/io/serverlessworkflow/impl/test/RunShellExecutorTest.java +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/RunShellExecutorTest.java @@ -15,14 +15,19 @@ */ package io.serverlessworkflow.impl.test; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + import io.serverlessworkflow.api.WorkflowReader; import io.serverlessworkflow.api.types.Workflow; import io.serverlessworkflow.impl.WorkflowApplication; import io.serverlessworkflow.impl.WorkflowModel; import io.serverlessworkflow.impl.executors.ProcessResult; import java.io.IOException; +import java.util.List; import java.util.Map; import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.EnabledOnOs; import org.junit.jupiter.api.condition.OS; @@ -30,74 +35,58 @@ @EnabledOnOs(value = OS.LINUX) public class RunShellExecutorTest { - @Test - void testEcho() throws IOException { - Workflow workflow = - WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - WorkflowModel model = appl.workflowDefinition(workflow).instance(Map.of()).start().join(); - SoftAssertions.assertSoftly( - softly -> { - ProcessResult result = model.as(ProcessResult.class).orElseThrow(); - softly.assertThat(result.code()).isEqualTo(0); - softly.assertThat(result.stderr()).isEmpty(); - softly.assertThat(result.stdout()).contains("Hello, anonymous"); - }); - } + private static WorkflowApplication appl; + + @BeforeAll + static void init() { + appl = WorkflowApplication.builder().withAllowedCommands(List.of("ls", "echo")).build(); + } + + @AfterAll + static void close() { + appl.close(); } @Test - void testEchoWithJqExpression() throws IOException { + void testEcho() throws IOException { Workflow workflow = - WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-jq.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - WorkflowModel model = - appl.workflowDefinition(workflow) - .instance(new Input(new User("John Doe"))) - .start() - .join(); - SoftAssertions.assertSoftly( - softly -> { - ProcessResult result = model.as(ProcessResult.class).orElseThrow(); - softly.assertThat(result.code()).isEqualTo(0); - softly.assertThat(result.stderr()).isEmpty(); - softly.assertThat(result.stdout()).contains("Hello, John Doe"); - }); - } + WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo.yaml"); + WorkflowModel model = appl.workflowDefinition(workflow).instance(Map.of()).start().join(); + SoftAssertions.assertSoftly( + softly -> { + ProcessResult result = model.as(ProcessResult.class).orElseThrow(); + softly.assertThat(result.code()).isEqualTo(0); + softly.assertThat(result.stderr()).isEmpty(); + softly.assertThat(result.stdout()).contains("Hello, anonymous"); + }); } @Test - void testEchoWithEnvironment() throws IOException { + void testEchoInvalid() throws IOException { Workflow workflow = - WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-with-env.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - WorkflowModel model = - appl.workflowDefinition(workflow).instance(Map.of("lastName", "Doe")).start().join(); - SoftAssertions.assertSoftly( - softly -> { - ProcessResult result = model.as(ProcessResult.class).orElseThrow(); - softly.assertThat(result.code()).isEqualTo(0); - softly.assertThat(result.stderr()).isEmpty(); - softly.assertThat(result.stdout()).contains("Hello John Doe from env!"); - }); - } + WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-invalid.yaml"); + assertThatThrownBy( + () -> + appl.workflowDefinition(workflow) + .instance(new Input(new User("John Doe"))) + .start() + .join()) + .hasCauseInstanceOf(SecurityException.class); } @Test - void testTouchAndCat() throws IOException { + void testEchoWithJqExpression() throws IOException { Workflow workflow = - WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/touch-cat.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - WorkflowModel model = - appl.workflowDefinition(workflow).instance(Map.of("lastName", "Doe")).start().join(); - SoftAssertions.assertSoftly( - softly -> { - ProcessResult result = model.as(ProcessResult.class).orElseThrow(); - softly.assertThat(result.code()).isEqualTo(0); - softly.assertThat(result.stderr()).isEmpty(); - softly.assertThat(result.stdout()).contains("hello world"); - }); - } + WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-jq.yaml"); + WorkflowModel model = + appl.workflowDefinition(workflow).instance(new Input(new User("John Doe"))).start().join(); + SoftAssertions.assertSoftly( + softly -> { + ProcessResult result = model.as(ProcessResult.class).orElseThrow(); + softly.assertThat(result.code()).isEqualTo(0); + softly.assertThat(result.stderr()).isEmpty(); + softly.assertThat(result.stdout()).contains("Hello, John Doe"); + }); } @Test @@ -105,87 +94,73 @@ void testMissingShellCommand() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath( "workflows-samples/run-shell/missing-shell-command.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - SoftAssertions.assertSoftly( - softly -> { - softly - .assertThatThrownBy( - () -> { - appl.workflowDefinition(workflow).instance(Map.of()).start().join(); - }) - .hasMessageContaining("Missing shell command in RunShell task configuration"); - }); - } + SoftAssertions.assertSoftly( + softly -> { + softly + .assertThatThrownBy( + () -> { + appl.workflowDefinition(workflow).instance(Map.of()).start().join(); + }) + .hasMessageContaining("Missing shell command in RunShell task configuration"); + }); } @Test - void testAwaitBehavior() throws IOException { + void testNonAwaitBehavior() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath( "workflows-samples/run-shell/echo-not-awaiting.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - Map inputMap = Map.of("full_name", "Matheus Cruz"); - WorkflowModel outputModel = - appl.workflowDefinition(workflow).instance(inputMap).start().join(); - SoftAssertions.assertSoftly( - softly -> { - softly.assertThat(outputModel.asMap().get()).isEqualTo(inputMap); - }); - } + Map inputMap = Map.of("full_name", "Matheus Cruz"); + WorkflowModel outputModel = appl.workflowDefinition(workflow).instance(inputMap).start().join(); + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(outputModel.asMap().get()).isEqualTo(inputMap); + }); } @Test void testStderr() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-stderr.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - Map inputMap = Map.of(); + Map inputMap = Map.of(); - WorkflowModel outputModel = - appl.workflowDefinition(workflow).instance(inputMap).start().join(); + WorkflowModel outputModel = appl.workflowDefinition(workflow).instance(inputMap).start().join(); - SoftAssertions.assertSoftly( - softly -> { - softly.assertThat(outputModel.asText()).isPresent(); - softly.assertThat(outputModel.asText().get()).isNotEmpty(); - softly.assertThat(outputModel.asText().get()).contains("ls:"); - }); - } + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(outputModel.asText()).isPresent(); + softly.assertThat(outputModel.asText().get()).isNotEmpty(); + softly.assertThat(outputModel.asText().get()).contains("ls:"); + }); } @Test void testExitCode() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-exitcode.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - Map inputMap = Map.of(); + Map inputMap = Map.of(); - WorkflowModel outputModel = - appl.workflowDefinition(workflow).instance(inputMap).start().join(); + WorkflowModel outputModel = appl.workflowDefinition(workflow).instance(inputMap).start().join(); - SoftAssertions.assertSoftly( - softly -> { - softly.assertThat(outputModel.asNumber()).isPresent(); - softly.assertThat(outputModel.asNumber().get()).isNotEqualTo(0); - }); - } + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(outputModel.asNumber()).isPresent(); + softly.assertThat(outputModel.asNumber().get()).isNotEqualTo(0); + }); } @Test void testNone() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-none.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - Map inputMap = Map.of(); + Map inputMap = Map.of(); - WorkflowModel outputModel = - appl.workflowDefinition(workflow).instance(inputMap).start().join(); + WorkflowModel outputModel = appl.workflowDefinition(workflow).instance(inputMap).start().join(); - SoftAssertions.assertSoftly( - softly -> { - softly.assertThat(outputModel.asJavaObject()).isEqualTo(Map.of()); - }); - } + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(outputModel.asJavaObject()).isEqualTo(Map.of()); + }); } @Test @@ -193,20 +168,18 @@ void testEchoWithArgsOnlyKey() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath( "workflows-samples/run-shell/echo-with-args-only-key.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - WorkflowModel model = - appl.workflowDefinition(workflow) - .instance(Map.of("firstName", "John", "lastName", "Doe")) - .start() - .join(); - SoftAssertions.assertSoftly( - softly -> { - ProcessResult result = model.as(ProcessResult.class).orElseThrow(); - softly.assertThat(result.code()).isEqualTo(0); - softly.assertThat(result.stderr()).isEmpty(); - softly.assertThat(result.stdout()).contains("Hello John Doe from args!"); - }); - } + WorkflowModel model = + appl.workflowDefinition(workflow) + .instance(Map.of("firstName", "John", "lastName", "Doe")) + .start() + .join(); + SoftAssertions.assertSoftly( + softly -> { + ProcessResult result = model.as(ProcessResult.class).orElseThrow(); + softly.assertThat(result.code()).isEqualTo(0); + softly.assertThat(result.stderr()).isEmpty(); + softly.assertThat(result.stdout()).contains("Hello John Doe"); + }); } @Test @@ -214,17 +187,15 @@ void testEchoWithArgsKeyValue() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath( "workflows-samples/run-shell/echo-with-args-key-value.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - WorkflowModel model = appl.workflowDefinition(workflow).instance(Map.of()).start().join(); - - SoftAssertions.assertSoftly( - softly -> { - ProcessResult result = model.as(ProcessResult.class).orElseThrow(); - softly.assertThat(result.code()).isEqualTo(0); - softly.assertThat(result.stderr()).isEmpty(); - softly.assertThat(result.stdout()).contains("--user=john --password=doe"); - }); - } + WorkflowModel model = appl.workflowDefinition(workflow).instance(Map.of()).start().join(); + + SoftAssertions.assertSoftly( + softly -> { + ProcessResult result = model.as(ProcessResult.class).orElseThrow(); + softly.assertThat(result.code()).isEqualTo(0); + softly.assertThat(result.stderr()).isEmpty(); + softly.assertThat(result.stdout()).contains("--user=john --password=doe"); + }); } @Test @@ -232,24 +203,22 @@ void testEchoWithArgsKeyValueJq() throws IOException { Workflow workflow = WorkflowReader.readWorkflowFromClasspath( "workflows-samples/run-shell/echo-with-args-key-value-jq.yaml"); - try (WorkflowApplication appl = WorkflowApplication.builder().build()) { - WorkflowModel model = - appl.workflowDefinition(workflow) - .instance( - Map.of( - "user", "john", - "passwordKey", "--password")) - .start() - .join(); - - SoftAssertions.assertSoftly( - softly -> { - ProcessResult result = model.as(ProcessResult.class).orElseThrow(); - softly.assertThat(result.code()).isEqualTo(0); - softly.assertThat(result.stderr()).isEmpty(); - softly.assertThat(result.stdout()).contains("--user=john --password=doe"); - }); - } + WorkflowModel model = + appl.workflowDefinition(workflow) + .instance( + Map.of( + "user", "john", + "passwordKey", "--password")) + .start() + .join(); + + SoftAssertions.assertSoftly( + softly -> { + ProcessResult result = model.as(ProcessResult.class).orElseThrow(); + softly.assertThat(result.code()).isEqualTo(0); + softly.assertThat(result.stderr()).isEmpty(); + softly.assertThat(result.stdout()).contains("--user=john --password=doe"); + }); } record Input(User user) {} diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-exitcode.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-exitcode.yaml index 7b3805886..6aab9bbf9 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-exitcode.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-exitcode.yaml @@ -1,11 +1,13 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-exit-code version: '0.1.0' do: - runShell: run: shell: - command: 'ls /nonexistent_directory' + command: ls + arguments: + - /nonexistent_directory return: code \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-invalid.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-invalid.yaml new file mode 100644 index 000000000..8910b0af4 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-invalid.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.1' + namespace: test + name: echo-invalid + version: '0.1.0' +do: + - runShell: + run: + shell: + command: 'echo "Hello, anonymous"' + return: all \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-jq.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-jq.yaml index 4e1b3ee93..40caefb4d 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-jq.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-jq.yaml @@ -1,11 +1,13 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-jq version: '0.1.0' do: - runShell: run: shell: - command: ${ "echo Hello, \(.user.name)" } + command: echo + arguments: + - ${"Hello, \(.user.name)"} return: all \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-none.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-none.yaml index dde32896a..3afcfc2a8 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-none.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-none.yaml @@ -1,11 +1,13 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-none version: '0.1.0' do: - runShell: run: shell: - command: 'echo "Serverless Workflow"' + command: echo + arguments: + - Open Workflow return: none \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-not-awaiting.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-not-awaiting.yaml index 464249571..7bc345e87 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-not-awaiting.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-not-awaiting.yaml @@ -1,13 +1,13 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-not-wait version: '0.1.0' do: - runShell: run: shell: - command: echo "hello world not awaiting ($FULL_NAME)" > /tmp/hello.txt && cat /tmp/hello.txt - environment: - FULL_NAME: ${.full_name} + command: echo + arguments: + - ${"hello world not awaiting \(.full_name)"} await: false \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-stderr.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-stderr.yaml index d2869be9c..be43398ed 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-stderr.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-stderr.yaml @@ -1,11 +1,13 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-stderr version: '0.1.0' do: - runShell: run: shell: - command: 'ls /nonexistent_directory' + command: ls + arguments: + - /nonexistent_directory return: stderr \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value-jq.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value-jq.yaml index a57b676dc..40510ffb0 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value-jq.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value-jq.yaml @@ -1,14 +1,14 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-key-value-jq version: '0.1.0' do: - runShell: run: shell: arguments: - '--user': '${.user}' - '${.passwordKey}': 'doe' + - ${"--user=\(.user)"} + - ${"\(.passwordKey)=doe"} command: echo return: all diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value.yaml index 916037c8b..aa5a25f72 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value.yaml @@ -1,14 +1,14 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-key-value version: '0.1.0' do: - runShell: run: shell: arguments: - '--user': 'john' - '--password': 'doe' + - '--user=john' + - '--password=doe' command: echo return: all diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-only-key.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-only-key.yaml index ab717b237..e87618cab 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-only-key.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-only-key.yaml @@ -1,17 +1,15 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-only-key version: '0.1.0' do: - runShell: run: shell: arguments: - 'Hello': - '${.firstName}': - '${.lastName}': - from: - 'args!': + - Hello + - ${.firstName} + - ${.lastName} command: echo return: all diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-env.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-env.yaml deleted file mode 100644 index 8361a3281..000000000 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-env.yaml +++ /dev/null @@ -1,14 +0,0 @@ -document: - dsl: '1.0.1' - namespace: test - name: run-shell-example - version: '0.1.0' -do: - - runShell: - run: - shell: - command: echo "Hello $FIRST_NAME $LAST_NAME from env!" - environment: - FIRST_NAME: John - LAST_NAME: ${.lastName} - return: all diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/echo.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/echo.yaml index 96dc715ac..310a860d6 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/echo.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo.yaml @@ -1,11 +1,13 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo version: '0.1.0' do: - runShell: run: shell: - command: 'echo "Hello, anonymous"' + command: 'echo' + arguments: + - Hello, anonymous return: all \ No newline at end of file diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/missing-shell-command.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/missing-shell-command.yaml index 81492510a..1ba528aa2 100644 --- a/impl/test/src/test/resources/workflows-samples/run-shell/missing-shell-command.yaml +++ b/impl/test/src/test/resources/workflows-samples/run-shell/missing-shell-command.yaml @@ -1,7 +1,7 @@ document: dsl: '1.0.1' namespace: test - name: run-shell-example + name: echo-missing version: '0.1.0' do: - missingShellCommand: diff --git a/impl/test/src/test/resources/workflows-samples/run-shell/touch-cat.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/touch-cat.yaml deleted file mode 100644 index b7d82d0b1..000000000 --- a/impl/test/src/test/resources/workflows-samples/run-shell/touch-cat.yaml +++ /dev/null @@ -1,15 +0,0 @@ -document: - dsl: '1.0.1' - namespace: test - name: run-shell-example - version: '0.1.0' -do: - - runShell: - run: - shell: - command: echo "hello world" > /tmp/hello.txt && cat /tmp/hello.txt - environment: - FIRST_NAME: John - LAST_NAME: ${.lastName} - return: all - diff --git a/types/src/main/resources/schema/workflow.yaml b/types/src/main/resources/schema/workflow.yaml index 2bbf0babe..8f0151e9e 100644 --- a/types/src/main/resources/schema/workflow.yaml +++ b/types/src/main/resources/schema/workflow.yaml @@ -924,10 +924,11 @@ $defs: title: ShellStdin description: A runtime expression, if any, to the shell command as standard input (stdin). arguments: - type: object + type: array title: ShellArguments - description: A list of the arguments of the shell command to run. - additionalProperties: true + description: A list of the arguments, if any, of the shell command to run. + items: + type: string environment: type: object title: ShellEnvironment @@ -1454,14 +1455,14 @@ $defs: uriTemplate: title: UriTemplate anyOf: - - title: LiteralUriTemplate - type: string - format: uri-template - pattern: "^(?!\\s*\\$\\{)(?=\\S)(([^:/?#]+):)?(//([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(\\S*))?$" - - title: LiteralUri - type: string - format: uri-reference - pattern: "^(?!\\s*\\$\\{)(?=\\S)(([^:/?#]+):)?(//([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(\\S*))?$" + - title: LiteralUriTemplate + type: string + format: uri-template + pattern: "^(?!\\s*\\$\\{)(?=\\S)(([^:/?#]+):)?(//([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(\\S*))?$" + - title: LiteralUri + type: string + format: uri-reference + pattern: "^(?!\\s*\\$\\{)(?=\\S)(([^:/?#]+):)?(//([^/?#\\s]*))?([^?#\\s]*)(\\?([^#\\s]*))?(#(\\S*))?$" endpoint: title: Endpoint description: Represents an endpoint.