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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -47,3 +47,5 @@ coverage.xml
.hypothesis/
.pytest_cache/
cover/

serverless-workflow/target
14 changes: 13 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
FROM python:3.13-slim

# Install system dependencies
# Install system dependencies including Java and Maven
RUN apt-get update && apt-get install -y \
curl \
openjdk-21-jdk \
maven \
&& rm -rf /var/lib/apt/lists/*

# Install uv
Expand All @@ -17,6 +19,16 @@ COPY pyproject.toml uv.lock ./
# Install dependencies
RUN uv sync --frozen

COPY serverless-workflow serverless-workflow
WORKDIR serverless-workflow

RUN mvn clean package
RUN mkdir -p /app/kogito && \
cp target/my-workflow-project-1.0-SNAPSHOT.jar /app/kogito/app.jar && \
cp target/dependency/* /app/kogito/ 2>/dev/null || true

WORKDIR /app

# Copy application code
COPY mcp_server.py ./
COPY tools/ ./tools/
Expand Down
81 changes: 81 additions & 0 deletions serverless-workflow/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.example</groupId>
<artifactId>my-workflow-project</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kogito.bom.artifact-id>kogito-bom</kogito.bom.artifact-id>
<kogito.bom.group-id>org.kie.kogito</kogito.bom.group-id>
<kogito.bom.version>10.1.0</kogito.bom.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>${kogito.bom.group-id}</groupId>
<artifactId>${kogito.bom.artifact-id}</artifactId>
<version>${kogito.bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-quarkus-serverless-workflow</artifactId>
<version>2.44.0.Alpha</version>
</dependency>

<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-serverless-workflow-runtime</artifactId>
<version>${kogito.bom.version}</version>
</dependency>

<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-serverless-workflow-executor-core</artifactId>
<version>${kogito.bom.version}</version>
</dependency>

<dependency>
<groupId>org.kie.kogito</groupId>
<artifactId>kogito-serverless-workflow-executor</artifactId>
<version>${kogito.bom.version}</version>
<type>pom</type>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<version>3.6.1</version>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.example;

import org.kie.kogito.serverless.workflow.executor.StaticWorkflowApplication;
import org.kie.kogito.serverless.workflow.models.JsonNodeModel;
import org.kie.kogito.serverless.workflow.utils.ServerlessWorkflowUtils;
import org.kie.kogito.serverless.workflow.utils.WorkflowFormat;
import io.serverlessworkflow.api.Workflow;
import org.kie.kogito.process.Process;

import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class DefinitionFileExecutor {

public static void main(String[] args) throws IOException {
System.out.printf("Initialize the workflow: %s\n", args[0]);

try (Reader reader = new FileReader(args[0]);
StaticWorkflowApplication application = StaticWorkflowApplication.create()) {
Workflow workflow = ServerlessWorkflowUtils.getWorkflow(reader, WorkflowFormat.JSON);
application.process(workflow);

JsonNodeModel result = application.execute(workflow, Collections.emptyMap());
System.out.printf("Execution information: %s\n", result);

List<String> registeredStates = workflow.getStates().stream()
.map(p -> p.getName())
.collect(Collectors.toList());

List<String> registeredFunctions = workflow.getFunctions().getFunctionDefs().stream()
.map(p -> p.getName())
.collect(Collectors.toList());

System.out.println("Registered functions:");
System.out.println(registeredFunctions);

System.out.println("Registered states:");
System.out.println(registeredStates);
System.out.println("Workflow is correct and compiled successfully");
} catch (Exception e) {
System.err.println("[ERROR] Workflow is not valid: " + e.getMessage());
System.exit(1);
}
}
}
2 changes: 2 additions & 0 deletions tools/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from .orchestrator_compile_workflow import compile_workflow
from .orchestrator_creation_workflow_rules import creation_workflow_rules
from .orchestrator_get_sample_workflow import orchestrator_get_sample_workflow
from .orchestrator_get_schema_rules import get_schema_rules
from .orchestrator_workflow_renderer import orchestrator_preview_workflow

__all__ = [
compile_workflow,
creation_workflow_rules,
get_schema_rules,
orchestrator_get_sample_workflow,
Expand Down
67 changes: 67 additions & 0 deletions tools/orchestrator_compile_workflow.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import logging
import os
import subprocess
import uuid

from .orchestrator_service import orchestrator_mcp

logger = logging.getLogger(__name__)


def get_command():
base_path = (
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ "/serverless-workflow"
)
return [
"java",
"-cp",
f"{base_path}/target/my-workflow-project-1.0-SNAPSHOT.jar:{base_path}/target/dependency/*",
"com.example.DefinitionFileExecutor",
]


@orchestrator_mcp.tool()
def compile_workflow(session_id: str, workflow: str) -> (bool, str):
"""
Compile and validate a rhdh orchestrator workflow by writing it to a
temporary file and executing the validation command.

Args:
session_id: The session identifier
workflow: The workflow content as a string

Returns:
A tuple of (success: bool, logs: str)
"""
logger.info(f"orchestrator_compile_workflow for session_id='{session_id}'")

# Generate unique filename using UUID
workflow_uuid = str(uuid.uuid4())
workflow_path = f"/tmp/workflow-{workflow_uuid}.sw.json"

try:
with open(workflow_path, "w") as f:
f.write(workflow)

cmd = get_command() + [workflow_path]
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

error handeling on sub process?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's alredy in the try catch, and we handle the output: success = result.returncode == 0
return success, logs


logs = result.stdout + result.stderr

try:
os.remove(workflow_path)
except OSError:
pass
Comment on lines +54 to +55
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be completed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what do you mean here. Like we try to remove, if not, what can be done? I mean, we don't want to break the server at all


success = result.returncode == 0
return success, logs

except Exception as e:
try:
os.remove(workflow_path)
except OSError:
pass

logger.error(f"Error compiling workflow: {e}")
return False, f"Error: {str(e)}"