Skip to content

Commit f45b6cd

Browse files
committed
Update code with RunnableTask abstraction
Signed-off-by: Matheus Cruz <[email protected]>
1 parent 9ed8d6b commit f45b6cd

File tree

8 files changed

+178
-6
lines changed

8 files changed

+178
-6
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
/*
2+
* Copyright 2020-Present The Serverless Workflow Specification Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.serverlessworkflow.impl.executors;
17+
18+
import io.serverlessworkflow.api.types.RunShell;
19+
import io.serverlessworkflow.api.types.RunTaskConfiguration;
20+
import io.serverlessworkflow.api.types.Shell;
21+
import io.serverlessworkflow.impl.TaskContext;
22+
import io.serverlessworkflow.impl.WorkflowApplication;
23+
import io.serverlessworkflow.impl.WorkflowContext;
24+
import io.serverlessworkflow.impl.WorkflowDefinition;
25+
import io.serverlessworkflow.impl.WorkflowModel;
26+
import io.serverlessworkflow.impl.WorkflowUtils;
27+
import io.serverlessworkflow.impl.expressions.ExpressionUtils;
28+
29+
import java.io.IOException;
30+
import java.io.OutputStream;
31+
import java.util.Map;
32+
import java.util.concurrent.CompletableFuture;
33+
34+
public class RunShellExecutor implements RunnableTask<RunShell> {
35+
36+
private ProcessResultSupplier processResultSupplier;
37+
private CommandSupplier commandSupplier;
38+
39+
@FunctionalInterface
40+
private interface ProcessResultSupplier {
41+
ProcessResult apply(TaskContext taskContext, ProcessBuilder processBuilder);
42+
}
43+
44+
private interface CommandSupplier {
45+
ProcessBuilder apply(TaskContext taskContext, WorkflowContext workflowContext);
46+
}
47+
48+
@Override
49+
public CompletableFuture<WorkflowModel> apply(
50+
WorkflowContext workflowContext, TaskContext taskContext, WorkflowModel input) {
51+
ProcessBuilder processBuilder = this.commandSupplier.apply(taskContext, workflowContext);
52+
ProcessResult processResult = this.processResultSupplier.apply(taskContext, processBuilder);
53+
return CompletableFuture.completedFuture(
54+
workflowContext.definition().application().modelFactory().fromAny(processResult));
55+
}
56+
57+
@Override
58+
public void init(RunShell taskConfiguration, WorkflowDefinition definition) {
59+
Shell shell = taskConfiguration.getShell();
60+
final String shellCommand = shell.getCommand();
61+
62+
this.commandSupplier =
63+
(taskContext, workflowContext) -> {
64+
WorkflowApplication application = definition.application();
65+
66+
String command =
67+
ExpressionUtils.isExpr(shellCommand)
68+
? WorkflowUtils.buildStringResolver(
69+
application, shellCommand, taskContext.input().asJavaObject())
70+
.apply(workflowContext, taskContext, taskContext.input())
71+
: shellCommand;
72+
73+
ProcessBuilder builder = new ProcessBuilder("sh", "-c", command);
74+
75+
if (shell.getEnvironment() != null
76+
&& shell.getEnvironment().getAdditionalProperties() != null) {
77+
for (Map.Entry<String, Object> entry :
78+
shell.getEnvironment().getAdditionalProperties().entrySet()) {
79+
String value =
80+
ExpressionUtils.isExpr(entry.getValue())
81+
? WorkflowUtils.buildStringResolver(
82+
application,
83+
entry.getValue().toString(),
84+
taskContext.input().asJavaObject())
85+
.apply(workflowContext, taskContext, taskContext.input())
86+
: entry.getValue().toString();
87+
builder.environment().put(entry.getKey(), value);
88+
}
89+
}
90+
91+
return builder;
92+
};
93+
94+
this.processResultSupplier =
95+
(taskContext, processBuilder) -> {
96+
if (shellCommand == null || shellCommand.isBlank()) {
97+
throw new IllegalStateException(
98+
"Missing shell command in RunShell task: " + taskContext.taskName());
99+
}
100+
101+
try {
102+
Process process = processBuilder.start();
103+
StringBuilder stdout = new StringBuilder();
104+
StringBuilder stderr = new StringBuilder();
105+
106+
process.getInputStream().transferTo(the(stdout));
107+
process.getErrorStream().transferTo(the(stderr));
108+
109+
int exitCode = process.waitFor();
110+
111+
return new ProcessResult(exitCode, stdout.toString().trim(), stderr.toString().trim());
112+
113+
} catch (IOException | InterruptedException e) {
114+
throw new RuntimeException(e);
115+
}
116+
};
117+
}
118+
119+
@Override
120+
public boolean accept(Class<? extends RunTaskConfiguration> clazz) {
121+
return RunShell.class.equals(clazz);
122+
}
123+
124+
125+
/**
126+
* Helper to create an OutputStream that writes to a StringBuilder
127+
* @param output the {@link StringBuilder} to write to
128+
* @return the {@link OutputStream}
129+
*/
130+
private static OutputStream the(StringBuilder output) {
131+
return new OutputStream() {
132+
@Override
133+
public void write(int b) {
134+
output.append((char) b);
135+
}
136+
};
137+
}
138+
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
io.serverlessworkflow.impl.executors.RunWorkflowExecutor
1+
io.serverlessworkflow.impl.executors.RunWorkflowExecutor
2+
io.serverlessworkflow.impl.executors.RunShellExecutor

impl/test/src/test/java/io/serverlessworkflow/impl/test/shell/RunTaskExecutorTest.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public class RunTaskExecutorTest {
3030
@Test
3131
void testEcho() throws IOException {
3232
Workflow workflow =
33-
WorkflowReader.readWorkflowFromClasspath("workflows-samples/shell-process/echo.yaml");
33+
WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo.yaml");
3434
try (WorkflowApplication appl = WorkflowApplication.builder().build()) {
3535
WorkflowModel model = appl.workflowDefinition(workflow).instance(Map.of()).start().join();
3636
SoftAssertions.assertSoftly(
@@ -46,7 +46,7 @@ void testEcho() throws IOException {
4646
@Test
4747
void testEchoWithJqExpression() throws IOException {
4848
Workflow workflow =
49-
WorkflowReader.readWorkflowFromClasspath("workflows-samples/shell-process/echo-jq.yaml");
49+
WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-jq.yaml");
5050
try (WorkflowApplication appl = WorkflowApplication.builder().build()) {
5151
WorkflowModel model =
5252
appl.workflowDefinition(workflow)
@@ -66,8 +66,7 @@ void testEchoWithJqExpression() throws IOException {
6666
@Test
6767
void testEchoWithEnvironment() throws IOException {
6868
Workflow workflow =
69-
WorkflowReader.readWorkflowFromClasspath(
70-
"workflows-samples/shell-process/echo-with-env.yaml");
69+
WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/echo-with-env.yaml");
7170
try (WorkflowApplication appl = WorkflowApplication.builder().build()) {
7271
WorkflowModel model =
7372
appl.workflowDefinition(workflow).instance(Map.of("lastName", "Doe")).start().join();
@@ -84,7 +83,7 @@ void testEchoWithEnvironment() throws IOException {
8483
@Test
8584
void testTouchAndCat() throws IOException {
8685
Workflow workflow =
87-
WorkflowReader.readWorkflowFromClasspath("workflows-samples/shell-process/touch-cat.yaml");
86+
WorkflowReader.readWorkflowFromClasspath("workflows-samples/run-shell/touch-cat.yaml");
8887
try (WorkflowApplication appl = WorkflowApplication.builder().build()) {
8988
WorkflowModel model =
9089
appl.workflowDefinition(workflow).instance(Map.of("lastName", "Doe")).start().join();
@@ -98,6 +97,26 @@ void testTouchAndCat() throws IOException {
9897
}
9998
}
10099

100+
@Test
101+
void testMissingShellCommand() throws IOException {
102+
Workflow workflow =
103+
WorkflowReader.readWorkflowFromClasspath(
104+
"workflows-samples/run-shell/missing-shell-command.yaml");
105+
try (WorkflowApplication appl = WorkflowApplication.builder().build()) {
106+
SoftAssertions.assertSoftly(
107+
softly -> {
108+
softly
109+
.assertThatThrownBy(
110+
() -> {
111+
appl.workflowDefinition(workflow).instance(Map.of()).start().join();
112+
})
113+
.hasCauseInstanceOf(IllegalStateException.class)
114+
.hasMessageContaining(
115+
"Missing shell command in RunShell task: missingShellCommand");
116+
});
117+
}
118+
}
119+
101120
record Input(User user) {}
102121

103122
record User(String name) {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
document:
2+
dsl: '1.0.1'
3+
namespace: test
4+
name: run-shell-example
5+
version: '0.1.0'
6+
do:
7+
- missingShellCommand:
8+
run:
9+
shell:
10+
command: ''
11+
environment:
12+
FIRST_NAME: John
13+
LAST_NAME: ${.lastName}
14+

0 commit comments

Comments
 (0)