diff --git a/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ProcessResult.java b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ProcessResult.java new file mode 100644 index 00000000..f04ded8c --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/ProcessResult.java @@ -0,0 +1,18 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.executors; + +public record ProcessResult(int code, String stdout, String stderr) {} 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 new file mode 100644 index 00000000..73fdb697 --- /dev/null +++ b/impl/core/src/main/java/io/serverlessworkflow/impl/executors/RunShellExecutor.java @@ -0,0 +1,182 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.executors; + +import io.serverlessworkflow.api.types.RunShell; +import io.serverlessworkflow.api.types.RunTaskConfiguration; +import io.serverlessworkflow.api.types.Shell; +import io.serverlessworkflow.impl.TaskContext; +import io.serverlessworkflow.impl.WorkflowApplication; +import io.serverlessworkflow.impl.WorkflowContext; +import io.serverlessworkflow.impl.WorkflowDefinition; +import io.serverlessworkflow.impl.WorkflowError; +import io.serverlessworkflow.impl.WorkflowException; +import io.serverlessworkflow.impl.WorkflowModel; +import io.serverlessworkflow.impl.WorkflowModelFactory; +import io.serverlessworkflow.impl.WorkflowUtils; +import io.serverlessworkflow.impl.expressions.ExpressionUtils; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class RunShellExecutor implements RunnableTask { + + private ShellResultSupplier shellResultSupplier; + private ProcessBuilderSupplier processBuilderSupplier; + + @FunctionalInterface + private interface ShellResultSupplier { + WorkflowModel apply( + TaskContext taskContext, WorkflowModel input, ProcessBuilder processBuilder); + } + + @FunctionalInterface + private interface ProcessBuilderSupplier { + ProcessBuilder apply(WorkflowContext workflowContext, TaskContext taskContext); + } + + @Override + public CompletableFuture apply( + WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) { + ProcessBuilder processBuilder = this.processBuilderSupplier.apply(workflowContext, taskContext); + return CompletableFuture.completedFuture( + this.shellResultSupplier.apply(taskContext, input, processBuilder)); + } + + @Override + public void init(RunShell taskConfiguration, WorkflowDefinition definition) { + Shell shell = taskConfiguration.getShell(); + final String shellCommand = shell.getCommand(); + + if (shellCommand == null || shellCommand.isBlank()) { + throw new IllegalStateException("Missing shell command in RunShell task configuration"); + } + this.processBuilderSupplier = + (workflowContext, taskContext) -> { + WorkflowApplication application = definition.application(); + + String command = + ExpressionUtils.isExpr(shellCommand) + ? WorkflowUtils.buildStringResolver( + application, shellCommand, taskContext.input().asJavaObject()) + .apply(workflowContext, taskContext, taskContext.input()) + : shellCommand; + + StringBuilder commandBuilder = new StringBuilder(command); + + if (shell.getArguments() != null + && shell.getArguments().getAdditionalProperties() != null) { + for (Map.Entry entry : + shell.getArguments().getAdditionalProperties().entrySet()) { + + String argKey = + ExpressionUtils.isExpr(entry.getKey()) + ? WorkflowUtils.buildStringResolver( + application, entry.getKey(), taskContext.input().asJavaObject()) + .apply(workflowContext, taskContext, taskContext.input()) + : entry.getKey(); + + if (entry.getValue() == null) { + commandBuilder.append(" ").append(argKey); + continue; + } + + String argValue = + ExpressionUtils.isExpr(entry.getValue()) + ? WorkflowUtils.buildStringResolver( + application, + entry.getValue().toString(), + taskContext.input().asJavaObject()) + .apply(workflowContext, taskContext, taskContext.input()) + : entry.getValue().toString(); + + commandBuilder.append(" ").append(argKey).append("=").append(argValue); + } + } + + // TODO: support Windows cmd.exe + ProcessBuilder builder = new ProcessBuilder("sh", "-c", commandBuilder.toString()); + + if (shell.getEnvironment() != null + && shell.getEnvironment().getAdditionalProperties() != null) { + for (Map.Entry entry : + shell.getEnvironment().getAdditionalProperties().entrySet()) { + String value = + ExpressionUtils.isExpr(entry.getValue()) + ? WorkflowUtils.buildStringResolver( + application, + entry.getValue().toString(), + taskContext.input().asJavaObject()) + .apply(workflowContext, taskContext, taskContext.input()) + : entry.getValue().toString(); + + // configure environments + builder.environment().put(entry.getKey(), value); + } + } + + return builder; + }; + + this.shellResultSupplier = + (taskContext, input, processBuilder) -> { + try { + Process process = processBuilder.start(); + + if (taskConfiguration.isAwait()) { + return buildResultFromProcess(taskConfiguration, definition, process); + } else { + return input; + } + + } catch (IOException | InterruptedException e) { + throw new WorkflowException(WorkflowError.runtime(taskContext, e).build(), e); + } + }; + } + + /** + * Builds the WorkflowModel result from the executed process. It waits for the process to finish + * and captures the exit code, stdout, and stderr based on the task configuration. + */ + private WorkflowModel buildResultFromProcess( + RunShell taskConfiguration, WorkflowDefinition definition, Process process) + throws IOException, InterruptedException { + + int exitCode = process.waitFor(); + + String stdout = new String(process.getInputStream().readAllBytes(), StandardCharsets.UTF_8); + String stderr = new String(process.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); + + RunTaskConfiguration.ProcessReturnType returnType = taskConfiguration.getReturn(); + + WorkflowModelFactory modelFactory = definition.application().modelFactory(); + + return switch (returnType) { + case ALL -> modelFactory.fromAny(new ProcessResult(exitCode, stdout.trim(), stderr.trim())); + case NONE -> modelFactory.fromNull(); + case CODE -> modelFactory.from(exitCode); + case STDOUT -> modelFactory.from(stdout.trim()); + case STDERR -> modelFactory.from(stderr.trim()); + }; + } + + @Override + public boolean accept(Class clazz) { + return RunShell.class.equals(clazz); + } +} diff --git a/impl/core/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.RunnableTask b/impl/core/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.RunnableTask index a26105df..7d2be4f9 100644 --- a/impl/core/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.RunnableTask +++ b/impl/core/src/main/resources/META-INF/services/io.serverlessworkflow.impl.executors.RunnableTask @@ -1 +1,2 @@ -io.serverlessworkflow.impl.executors.RunWorkflowExecutor \ No newline at end of file +io.serverlessworkflow.impl.executors.RunWorkflowExecutor +io.serverlessworkflow.impl.executors.RunShellExecutor \ No newline at end of file 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 new file mode 100644 index 00000000..bd96d225 --- /dev/null +++ b/impl/test/src/test/java/io/serverlessworkflow/impl/test/RunShellExecutorTest.java @@ -0,0 +1,263 @@ +/* + * Copyright 2020-Present The Serverless Workflow Specification Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.serverlessworkflow.impl.test; + +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.nio.file.Files; +import java.nio.file.Path; +import java.util.Map; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +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"); + }); + } + } + + @Test + void testEchoWithJqExpression() 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"); + }); + } + } + + @Test + void testEchoWithEnvironment() 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!"); + }); + } + } + + @Test + void testTouchAndCat() 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"); + }); + } + } + + @Test + 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"); + }); + } + } + + @Test + void testAwaitBehavior() 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(); + + String content = Files.readString(Path.of("/tmp/hello.txt")); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(content).contains("hello world not awaiting (Matheus Cruz)"); + 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(); + + 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: cannot access"); + softly.assertThat(outputModel.asText().get()).contains("No such file or directory"); + }); + } + } + + @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(); + + WorkflowModel outputModel = + appl.workflowDefinition(workflow).instance(inputMap).start().join(); + + 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(); + + WorkflowModel outputModel = + appl.workflowDefinition(workflow).instance(inputMap).start().join(); + + SoftAssertions.assertSoftly( + softly -> { + softly.assertThat(outputModel.asJavaObject()).isNull(); + }); + } + } + + @Test + 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!"); + }); + } + } + + @Test + 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"); + }); + } + } + + @Test + 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"); + }); + } + } + + record Input(User user) {} + + record User(String name) {} +} 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 new file mode 100644 index 00000000..7b380588 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-exitcode.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - runShell: + run: + shell: + command: 'ls /nonexistent_directory' + return: code \ 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 new file mode 100644 index 00000000..4e1b3ee9 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-jq.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - runShell: + run: + shell: + command: ${ "echo 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 new file mode 100644 index 00000000..dde32896 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-none.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - runShell: + run: + shell: + command: 'echo "Serverless 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 new file mode 100644 index 00000000..46424957 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-not-awaiting.yaml @@ -0,0 +1,13 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + 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} + 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 new file mode 100644 index 00000000..d2869be9 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-stderr.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - runShell: + run: + shell: + command: 'ls /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 new file mode 100644 index 00000000..a57b676d --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value-jq.yaml @@ -0,0 +1,14 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - runShell: + run: + shell: + arguments: + '--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 new file mode 100644 index 00000000..916037c8 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-key-value.yaml @@ -0,0 +1,14 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - runShell: + run: + shell: + arguments: + '--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 new file mode 100644 index 00000000..ab717b23 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-args-only-key.yaml @@ -0,0 +1,17 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - runShell: + run: + shell: + arguments: + 'Hello': + '${.firstName}': + '${.lastName}': + from: + 'args!': + 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 new file mode 100644 index 00000000..8361a328 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo-with-env.yaml @@ -0,0 +1,14 @@ +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 new file mode 100644 index 00000000..96dc715a --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/echo.yaml @@ -0,0 +1,11 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + 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/missing-shell-command.yaml b/impl/test/src/test/resources/workflows-samples/run-shell/missing-shell-command.yaml new file mode 100644 index 00000000..81492510 --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/missing-shell-command.yaml @@ -0,0 +1,14 @@ +document: + dsl: '1.0.1' + namespace: test + name: run-shell-example + version: '0.1.0' +do: + - missingShellCommand: + run: + shell: + command: '' + environment: + FIRST_NAME: John + LAST_NAME: ${.lastName} + 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 new file mode 100644 index 00000000..b7d82d0b --- /dev/null +++ b/impl/test/src/test/resources/workflows-samples/run-shell/touch-cat.yaml @@ -0,0 +1,15 @@ +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 +