mirror of
https://github.com/FuzzingLabs/fuzzforge_ai.git
synced 2026-05-21 06:16:51 +02:00
chore: Complete Temporal migration with updated CLI/SDK/docs
This commit includes all remaining Temporal migration changes: ## CLI Updates (cli/) - Updated workflow execution commands for Temporal - Enhanced error handling and exceptions - Updated dependencies in uv.lock ## SDK Updates (sdk/) - Client methods updated for Temporal workflows - Updated models for new workflow execution - Updated dependencies in uv.lock ## Documentation Updates (docs/) - Architecture documentation for Temporal - Workflow concept documentation - Resource management documentation (new) - Debugging guide (new) - Updated tutorials and how-to guides - Troubleshooting updates ## README Updates - Main README with Temporal instructions - Backend README - CLI README - SDK README ## Other - Updated IMPLEMENTATION_STATUS.md - Removed old vulnerable_app.tar.gz These changes complete the Temporal migration and ensure the CLI/SDK work correctly with the new backend.
This commit is contained in:
+45
-2
@@ -153,10 +153,10 @@ fuzzforge workflows parameters security_assessment --no-interactive
|
||||
### Workflow Execution
|
||||
|
||||
#### `fuzzforge workflow <workflow> <target-path>`
|
||||
Execute a security testing workflow.
|
||||
Execute a security testing workflow with **automatic file upload**.
|
||||
|
||||
```bash
|
||||
# Basic execution
|
||||
# Basic execution - CLI automatically detects local files and uploads them
|
||||
fuzzforge workflow security_assessment /path/to/code
|
||||
|
||||
# With parameters
|
||||
@@ -172,6 +172,49 @@ fuzzforge workflow security_assessment /path/to/code \
|
||||
fuzzforge workflow security_assessment /path/to/code --wait
|
||||
```
|
||||
|
||||
**Automatic File Upload Behavior:**
|
||||
|
||||
The CLI intelligently handles target files based on whether they exist locally:
|
||||
|
||||
1. **Local file/directory exists** → **Automatic upload to MinIO**:
|
||||
- CLI creates a compressed tarball (`.tar.gz`) for directories
|
||||
- Uploads via HTTP to backend API
|
||||
- Backend stores in MinIO with unique `target_id`
|
||||
- Worker downloads from MinIO when ready to analyze
|
||||
- ✅ **Works from any machine** (no shared filesystem needed)
|
||||
|
||||
2. **Path doesn't exist locally** → **Path-based submission** (legacy):
|
||||
- Path is sent to backend as-is
|
||||
- Backend expects target to be accessible on its filesystem
|
||||
- ⚠️ Only works when CLI and backend share filesystem
|
||||
|
||||
**Example workflow:**
|
||||
```bash
|
||||
$ ff workflow security_assessment ./my-project
|
||||
|
||||
🔧 Getting workflow information for: security_assessment
|
||||
📦 Detected local directory: ./my-project (21 files)
|
||||
🗜️ Creating compressed tarball...
|
||||
📤 Uploading to backend (0.01 MB)...
|
||||
✅ Upload complete! Target ID: 548193a1-f73f-4ec1-8068-19ec2660b8e4
|
||||
|
||||
🎯 Executing workflow:
|
||||
Workflow: security_assessment
|
||||
Target: my-project.tar.gz (uploaded)
|
||||
Volume Mode: ro
|
||||
Status: 🔄 RUNNING
|
||||
|
||||
✅ Workflow started successfully!
|
||||
Execution ID: security_assessment-52781925
|
||||
```
|
||||
|
||||
**Upload Details:**
|
||||
- **Max file size**: 10 GB (configurable on backend)
|
||||
- **Compression**: Automatic for directories (reduces upload time)
|
||||
- **Storage**: Files stored in MinIO (S3-compatible)
|
||||
- **Lifecycle**: Automatic cleanup after 7 days
|
||||
- **Caching**: Workers cache downloaded targets for faster repeated workflows
|
||||
|
||||
**Options:**
|
||||
- `--param, -p` - Parameter in key=value format (can be used multiple times)
|
||||
- `--param-file, -f` - JSON file containing parameters
|
||||
|
||||
@@ -77,7 +77,7 @@ def execute_workflow_submission(
|
||||
timeout: Optional[int],
|
||||
interactive: bool
|
||||
) -> Any:
|
||||
"""Handle the workflow submission process"""
|
||||
"""Handle the workflow submission process with file upload"""
|
||||
# Get workflow metadata for parameter validation
|
||||
console.print(f"🔧 Getting workflow information for: {workflow}")
|
||||
workflow_meta = client.get_workflow_metadata(workflow)
|
||||
@@ -87,7 +87,7 @@ def execute_workflow_submission(
|
||||
if interactive and workflow_meta.parameters.get("properties"):
|
||||
properties = workflow_meta.parameters.get("properties", {})
|
||||
required_params = set(workflow_meta.parameters.get("required", []))
|
||||
defaults = param_response.defaults
|
||||
defaults = param_response.default_parameters
|
||||
|
||||
missing_required = required_params - set(parameters.keys())
|
||||
|
||||
@@ -131,14 +131,6 @@ def execute_workflow_submission(
|
||||
f"one of: {', '.join(workflow_meta.supported_volume_modes)}"
|
||||
)
|
||||
|
||||
# Create submission
|
||||
submission = WorkflowSubmission(
|
||||
target_path=target_path,
|
||||
volume_mode=volume_mode,
|
||||
parameters=parameters,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
# Show submission summary
|
||||
console.print(f"\n🎯 [bold]Executing workflow:[/bold]")
|
||||
console.print(f" Workflow: {workflow}")
|
||||
@@ -149,6 +141,22 @@ def execute_workflow_submission(
|
||||
if timeout:
|
||||
console.print(f" Timeout: {timeout}s")
|
||||
|
||||
# Check if target path exists locally
|
||||
target_path_obj = Path(target_path)
|
||||
use_upload = target_path_obj.exists()
|
||||
|
||||
if use_upload:
|
||||
# Show file/directory info
|
||||
if target_path_obj.is_dir():
|
||||
num_files = sum(1 for _ in target_path_obj.rglob("*") if _.is_file())
|
||||
console.print(f" Upload: Directory with {num_files} files")
|
||||
else:
|
||||
size_mb = target_path_obj.stat().st_size / (1024 * 1024)
|
||||
console.print(f" Upload: File ({size_mb:.2f} MB)")
|
||||
else:
|
||||
console.print(f" [yellow]⚠️ Warning: Target path does not exist locally[/yellow]")
|
||||
console.print(f" [yellow] Attempting to use path-based submission (backend must have access)[/yellow]")
|
||||
|
||||
# Only ask for confirmation in interactive mode
|
||||
if interactive:
|
||||
if not Confirm.ask("\nExecute workflow?", default=True, console=console):
|
||||
@@ -160,32 +168,75 @@ def execute_workflow_submission(
|
||||
# Submit the workflow with enhanced progress
|
||||
console.print(f"\n🚀 Executing workflow: [bold yellow]{workflow}[/bold yellow]")
|
||||
|
||||
steps = [
|
||||
"Validating workflow configuration",
|
||||
"Connecting to FuzzForge API",
|
||||
"Uploading parameters and settings",
|
||||
"Creating workflow deployment",
|
||||
"Initializing execution environment"
|
||||
]
|
||||
if use_upload:
|
||||
# Use new upload-based submission
|
||||
steps = [
|
||||
"Validating workflow configuration",
|
||||
"Creating tarball (if directory)",
|
||||
"Uploading target to backend",
|
||||
"Starting workflow execution",
|
||||
"Initializing execution environment"
|
||||
]
|
||||
|
||||
with step_progress(steps, f"Executing {workflow}") as progress:
|
||||
progress.next_step() # Validating
|
||||
time.sleep(PROGRESS_STEP_DELAYS["validating"])
|
||||
with step_progress(steps, f"Executing {workflow}") as progress:
|
||||
progress.next_step() # Validating
|
||||
time.sleep(PROGRESS_STEP_DELAYS["validating"])
|
||||
|
||||
progress.next_step() # Connecting
|
||||
time.sleep(PROGRESS_STEP_DELAYS["connecting"])
|
||||
progress.next_step() # Creating tarball
|
||||
time.sleep(PROGRESS_STEP_DELAYS["connecting"])
|
||||
|
||||
progress.next_step() # Uploading
|
||||
response = client.submit_workflow(workflow, submission)
|
||||
time.sleep(PROGRESS_STEP_DELAYS["uploading"])
|
||||
progress.next_step() # Uploading
|
||||
# Use the new upload method
|
||||
response = client.submit_workflow_with_upload(
|
||||
workflow_name=workflow,
|
||||
target_path=target_path,
|
||||
parameters=parameters,
|
||||
volume_mode=volume_mode,
|
||||
timeout=timeout
|
||||
)
|
||||
time.sleep(PROGRESS_STEP_DELAYS["uploading"])
|
||||
|
||||
progress.next_step() # Creating deployment
|
||||
time.sleep(PROGRESS_STEP_DELAYS["creating"])
|
||||
progress.next_step() # Starting
|
||||
time.sleep(PROGRESS_STEP_DELAYS["creating"])
|
||||
|
||||
progress.next_step() # Initializing
|
||||
time.sleep(PROGRESS_STEP_DELAYS["initializing"])
|
||||
progress.next_step() # Initializing
|
||||
time.sleep(PROGRESS_STEP_DELAYS["initializing"])
|
||||
|
||||
progress.complete(f"Workflow started successfully!")
|
||||
progress.complete(f"Workflow started successfully!")
|
||||
else:
|
||||
# Fall back to path-based submission (for backward compatibility)
|
||||
steps = [
|
||||
"Validating workflow configuration",
|
||||
"Connecting to FuzzForge API",
|
||||
"Submitting workflow parameters",
|
||||
"Creating workflow deployment",
|
||||
"Initializing execution environment"
|
||||
]
|
||||
|
||||
with step_progress(steps, f"Executing {workflow}") as progress:
|
||||
progress.next_step() # Validating
|
||||
time.sleep(PROGRESS_STEP_DELAYS["validating"])
|
||||
|
||||
progress.next_step() # Connecting
|
||||
time.sleep(PROGRESS_STEP_DELAYS["connecting"])
|
||||
|
||||
progress.next_step() # Submitting
|
||||
submission = WorkflowSubmission(
|
||||
target_path=target_path,
|
||||
volume_mode=volume_mode,
|
||||
parameters=parameters,
|
||||
timeout=timeout
|
||||
)
|
||||
response = client.submit_workflow(workflow, submission)
|
||||
time.sleep(PROGRESS_STEP_DELAYS["uploading"])
|
||||
|
||||
progress.next_step() # Creating deployment
|
||||
time.sleep(PROGRESS_STEP_DELAYS["creating"])
|
||||
|
||||
progress.next_step() # Initializing
|
||||
time.sleep(PROGRESS_STEP_DELAYS["initializing"])
|
||||
|
||||
progress.complete(f"Workflow started successfully!")
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ def workflow_parameters(
|
||||
parameters = {}
|
||||
properties = workflow.parameters.get("properties", {})
|
||||
required_params = set(workflow.parameters.get("required", []))
|
||||
defaults = param_response.defaults
|
||||
defaults = param_response.default_parameters
|
||||
|
||||
if interactive:
|
||||
console.print("🔧 Enter parameter values (press Enter for default):\n")
|
||||
|
||||
@@ -430,8 +430,9 @@ def validate_run_id(run_id: str) -> str:
|
||||
if not run_id or len(run_id) < 8:
|
||||
raise ValidationError("run_id", run_id, "at least 8 characters")
|
||||
|
||||
if not run_id.replace('-', '').isalnum():
|
||||
raise ValidationError("run_id", run_id, "alphanumeric characters and hyphens only")
|
||||
# Allow alphanumeric characters, hyphens, and underscores
|
||||
if not run_id.replace('-', '').replace('_', '').isalnum():
|
||||
raise ValidationError("run_id", run_id, "alphanumeric characters, hyphens, and underscores only")
|
||||
|
||||
return run_id
|
||||
|
||||
|
||||
Generated
+3
-3
@@ -1257,7 +1257,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "fuzzforge-ai"
|
||||
version = "0.1.0"
|
||||
version = "0.6.0"
|
||||
source = { editable = "../ai" }
|
||||
dependencies = [
|
||||
{ name = "a2a-sdk" },
|
||||
@@ -1303,7 +1303,7 @@ dev = [
|
||||
|
||||
[[package]]
|
||||
name = "fuzzforge-cli"
|
||||
version = "0.1.0"
|
||||
version = "0.6.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "fuzzforge-ai" },
|
||||
@@ -1347,7 +1347,7 @@ provides-extras = ["dev"]
|
||||
|
||||
[[package]]
|
||||
name = "fuzzforge-sdk"
|
||||
version = "0.1.0"
|
||||
version = "0.6.0"
|
||||
source = { editable = "../sdk" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
|
||||
Reference in New Issue
Block a user