feat: simplify OpenRPC API and reorganize examples
- Simplified RunnerConfig to just name, command, and optional env - Removed RunnerType and ProcessManagerType enums - Removed db_path, redis_url, binary_path from config - Made runner name also serve as queue name (no separate queue param) - Added secret-based authentication to all runner management methods - Created comprehensive osiris_openrpc example - Archived old examples to _archive/ - Updated client API to match simplified supervisor interface
This commit is contained in:
94
examples/osiris_openrpc/README.md
Normal file
94
examples/osiris_openrpc/README.md
Normal file
@@ -0,0 +1,94 @@
|
||||
# OSIRIS + OpenRPC Comprehensive Example
|
||||
|
||||
This example demonstrates the complete workflow of using Hero Supervisor with OSIRIS runners via OpenRPC.
|
||||
|
||||
## What This Example Does
|
||||
|
||||
1. **Builds and starts** Hero Supervisor with OpenRPC server enabled
|
||||
2. **Builds** the OSIRIS runner binary
|
||||
3. **Connects** an OpenRPC client to the supervisor
|
||||
4. **Registers and starts** an OSIRIS runner
|
||||
5. **Dispatches multiple jobs** via OpenRPC:
|
||||
- Create a Note
|
||||
- Create an Event
|
||||
- Query stored data
|
||||
- Test access control (expected to fail)
|
||||
6. **Monitors** job execution and results
|
||||
7. **Gracefully shuts down** all components
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Redis server running on `localhost:6379`
|
||||
- Rust toolchain installed
|
||||
- Both `supervisor` and `runner_rust` crates available
|
||||
|
||||
## Running the Example
|
||||
|
||||
```bash
|
||||
cargo run --example osiris_openrpc
|
||||
```
|
||||
|
||||
## Job Scripts
|
||||
|
||||
The example uses separate Rhai script files for each job:
|
||||
|
||||
- `note.rhai` - Creates and stores a Note object
|
||||
- `event.rhai` - Creates and stores an Event object
|
||||
- `query.rhai` - Queries and retrieves stored objects
|
||||
- `access_denied.rhai` - Tests access control (should fail)
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ This Example │
|
||||
│ (OpenRPC │
|
||||
│ Client) │
|
||||
└────────┬────────┘
|
||||
│ JSON-RPC
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ Supervisor │
|
||||
│ (OpenRPC │
|
||||
│ Server) │
|
||||
└────────┬────────┘
|
||||
│ Redis Queue
|
||||
↓
|
||||
┌─────────────────┐
|
||||
│ OSIRIS Runner │
|
||||
│ (Rhai Engine │
|
||||
│ + HeroDB) │
|
||||
└─────────────────┘
|
||||
```
|
||||
|
||||
## Key Features Demonstrated
|
||||
|
||||
- **Automatic binary building** using escargot
|
||||
- **OpenRPC communication** between client and supervisor
|
||||
- **Runner registration** with configuration
|
||||
- **Job dispatching** with signatories
|
||||
- **Context-based access control** in OSIRIS
|
||||
- **Typed object storage** (Note, Event)
|
||||
- **Graceful shutdown** and cleanup
|
||||
|
||||
## Expected Output
|
||||
|
||||
The example will:
|
||||
1. ✅ Create a Note successfully
|
||||
2. ✅ Create an Event successfully
|
||||
3. ✅ Query and retrieve stored objects
|
||||
4. ✅ Deny access for unauthorized participants
|
||||
5. ✅ Clean up all resources
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Redis Connection Error:**
|
||||
- Ensure Redis is running: `redis-server`
|
||||
|
||||
**Build Errors:**
|
||||
- Ensure both supervisor and runner_rust crates are available
|
||||
- Check that all dependencies are up to date
|
||||
|
||||
**OpenRPC Connection Error:**
|
||||
- Port 3030 might be in use
|
||||
- Check supervisor logs for startup issues
|
||||
8
examples/osiris_openrpc/access_denied.rhai
Normal file
8
examples/osiris_openrpc/access_denied.rhai
Normal file
@@ -0,0 +1,8 @@
|
||||
print("Attempting to access context with non-signatories...");
|
||||
print("Participants: [dave, eve]");
|
||||
print("Signatories: [alice, bob, charlie]");
|
||||
|
||||
// This should fail because neither dave nor eve are signatories
|
||||
let ctx = get_context(["dave", "eve"]);
|
||||
|
||||
"This should not succeed!"
|
||||
18
examples/osiris_openrpc/event.rhai
Normal file
18
examples/osiris_openrpc/event.rhai
Normal file
@@ -0,0 +1,18 @@
|
||||
print("Creating context for [alice, bob]...");
|
||||
let ctx = get_context(["alice", "bob"]);
|
||||
print("✓ Context ID: " + ctx.context_id());
|
||||
|
||||
print("\nCreating event...");
|
||||
let event = event("events")
|
||||
.title("Team Retrospective")
|
||||
.description("Review what went well and areas for improvement")
|
||||
.location("Virtual - Zoom Room A")
|
||||
.category("retrospective");
|
||||
|
||||
print("✓ Event created");
|
||||
|
||||
print("\nStoring event in context...");
|
||||
ctx.save(event);
|
||||
print("✓ Event stored");
|
||||
|
||||
"Event 'Team Retrospective' created and stored successfully"
|
||||
239
examples/osiris_openrpc/main.rs
Normal file
239
examples/osiris_openrpc/main.rs
Normal file
@@ -0,0 +1,239 @@
|
||||
///! Comprehensive OSIRIS + OpenRPC Example
|
||||
///!
|
||||
///! This example demonstrates:
|
||||
///! 1. Starting a Hero Supervisor with OpenRPC server
|
||||
///! 2. Starting an OSIRIS runner
|
||||
///! 3. Registering the runner with the supervisor
|
||||
///! 4. Dispatching multiple OSIRIS jobs via OpenRPC
|
||||
///! 5. Monitoring job execution
|
||||
///! 6. Graceful shutdown
|
||||
///!
|
||||
///! Usage:
|
||||
///! ```bash
|
||||
///! cargo run --example osiris_openrpc
|
||||
///! ```
|
||||
|
||||
use hero_supervisor_openrpc_client::{SupervisorClient, RunnerConfig, JobBuilder};
|
||||
use std::time::Duration;
|
||||
use escargot::CargoBuild;
|
||||
use std::process::Stdio;
|
||||
use tokio::time::sleep;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("🚀 OSIRIS + OpenRPC Comprehensive Example");
|
||||
println!("=========================================\n");
|
||||
|
||||
// ========================================================================
|
||||
// STEP 1: Build and start supervisor with OpenRPC
|
||||
// ========================================================================
|
||||
println!("Step 1: Building and starting supervisor");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
let supervisor_binary = CargoBuild::new()
|
||||
.bin("supervisor")
|
||||
.current_release()
|
||||
.manifest_path("../supervisor/Cargo.toml")
|
||||
.run()?;
|
||||
|
||||
println!("✅ Supervisor binary built");
|
||||
|
||||
let mut supervisor = supervisor_binary.command()
|
||||
.arg("--redis-url")
|
||||
.arg("redis://localhost:6379")
|
||||
.arg("--openrpc")
|
||||
.arg("--openrpc-port")
|
||||
.arg("3030")
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
|
||||
println!("✅ Supervisor started on port 3030");
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
|
||||
// ========================================================================
|
||||
// STEP 2: Build OSIRIS runner
|
||||
// ========================================================================
|
||||
println!("\nStep 2: Building OSIRIS runner");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
let runner_binary = CargoBuild::new()
|
||||
.bin("runner_osiris")
|
||||
.current_release()
|
||||
.manifest_path("../runner_rust/Cargo.toml")
|
||||
.run()?;
|
||||
|
||||
println!("✅ OSIRIS runner binary built");
|
||||
|
||||
// ========================================================================
|
||||
// STEP 3: Connect OpenRPC client
|
||||
// ========================================================================
|
||||
println!("\nStep 3: Connecting OpenRPC client");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
let client = SupervisorClient::new("http://127.0.0.1:3030")?;
|
||||
println!("✅ Connected to supervisor\n");
|
||||
|
||||
// ========================================================================
|
||||
// STEP 4: Register and start OSIRIS runner
|
||||
// ========================================================================
|
||||
println!("Step 4: Registering OSIRIS runner");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
let runner_path = runner_binary.path().to_string_lossy();
|
||||
let db_path = "/tmp/osiris_openrpc.db";
|
||||
|
||||
let command = format!(
|
||||
"{} osiris_runner --db-path {} --redis-url redis://localhost:6379",
|
||||
runner_path, db_path
|
||||
);
|
||||
|
||||
let runner_config = RunnerConfig {
|
||||
name: "osiris_runner".to_string(),
|
||||
command,
|
||||
env: None,
|
||||
};
|
||||
|
||||
client.add_runner("admin_secret", runner_config).await?;
|
||||
println!("✅ Runner registered: osiris_runner");
|
||||
|
||||
client.start_runner("admin_secret", "osiris_runner").await?;
|
||||
println!("✅ Runner started\n");
|
||||
|
||||
sleep(Duration::from_secs(2)).await;
|
||||
|
||||
// ========================================================================
|
||||
// STEP 5: Load job scripts
|
||||
// ========================================================================
|
||||
println!("Step 5: Loading job scripts");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
let note_script = std::fs::read_to_string("examples/osiris_openrpc/note.rhai")?;
|
||||
let event_script = std::fs::read_to_string("examples/osiris_openrpc/event.rhai")?;
|
||||
let query_script = std::fs::read_to_string("examples/osiris_openrpc/query.rhai")?;
|
||||
let access_denied_script = std::fs::read_to_string("examples/osiris_openrpc/access_denied.rhai")?;
|
||||
|
||||
println!("✅ Loaded 4 job scripts\n");
|
||||
|
||||
// ========================================================================
|
||||
// STEP 6: Dispatch jobs via OpenRPC
|
||||
// ========================================================================
|
||||
println!("Step 6: Dispatching jobs");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
// Job 1: Create Note
|
||||
println!("📝 Job 1: Creating Note...");
|
||||
let job1 = JobBuilder::new()
|
||||
.caller_id("openrpc_client")
|
||||
.context_id("osiris_demo")
|
||||
.payload(¬e_script)
|
||||
.runner("osiris_runner")
|
||||
.executor("rhai")
|
||||
.timeout(30)
|
||||
.signature("alice", "")
|
||||
.signature("bob", "")
|
||||
.build()?;
|
||||
|
||||
let job1_result = client.run_job("user_secret", job1).await;
|
||||
|
||||
match job1_result {
|
||||
Ok(result) => println!("✅ {:?}\n", result),
|
||||
Err(e) => println!("❌ Job failed: {}\n", e),
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
|
||||
// Job 2: Create Event
|
||||
println!("📅 Job 2: Creating Event...");
|
||||
let job2 = JobBuilder::new()
|
||||
.caller_id("openrpc_client")
|
||||
.context_id("osiris_demo")
|
||||
.payload(&event_script)
|
||||
.runner("osiris_runner")
|
||||
.executor("rhai")
|
||||
.timeout(30)
|
||||
.signature("alice", "")
|
||||
.signature("bob", "")
|
||||
.build()?;
|
||||
|
||||
let job2_result = client.run_job("user_secret", job2).await;
|
||||
|
||||
match job2_result {
|
||||
Ok(result) => println!("✅ {:?}\n", result),
|
||||
Err(e) => println!("❌ Job failed: {}\n", e),
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
|
||||
// Job 3: Query Data
|
||||
println!("🔍 Job 3: Querying Data...");
|
||||
let job3 = JobBuilder::new()
|
||||
.caller_id("openrpc_client")
|
||||
.context_id("osiris_demo")
|
||||
.payload(&query_script)
|
||||
.runner("osiris_runner")
|
||||
.executor("rhai")
|
||||
.timeout(30)
|
||||
.signature("alice", "")
|
||||
.signature("bob", "")
|
||||
.signature("charlie", "")
|
||||
.build()?;
|
||||
|
||||
let job3_result = client.run_job("user_secret", job3).await;
|
||||
|
||||
match job3_result {
|
||||
Ok(result) => println!("✅ {:?}\n", result),
|
||||
Err(e) => println!("❌ Job failed: {}\n", e),
|
||||
}
|
||||
|
||||
sleep(Duration::from_secs(1)).await;
|
||||
|
||||
// Job 4: Access Control Test (should fail)
|
||||
println!("🔒 Job 4: Testing Access Control (expected to fail)...");
|
||||
let job4 = JobBuilder::new()
|
||||
.caller_id("openrpc_client")
|
||||
.context_id("osiris_demo")
|
||||
.payload(&access_denied_script)
|
||||
.runner("osiris_runner")
|
||||
.executor("rhai")
|
||||
.timeout(30)
|
||||
.signature("alice", "")
|
||||
.signature("bob", "")
|
||||
.signature("charlie", "")
|
||||
.build()?;
|
||||
|
||||
let job4_result = client.run_job("user_secret", job4).await;
|
||||
|
||||
match job4_result {
|
||||
Ok(result) => println!("❌ Unexpected success: {:?}\n", result),
|
||||
Err(e) => println!("✅ Access denied as expected: {}\n", e),
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// STEP 7: Check runner status
|
||||
// ========================================================================
|
||||
println!("\nStep 7: Checking runner status");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
let status = client.get_runner_status("admin_secret", "osiris_runner").await?;
|
||||
println!("Runner status: {:?}\n", status);
|
||||
|
||||
// ========================================================================
|
||||
// STEP 8: Cleanup
|
||||
// ========================================================================
|
||||
println!("Step 8: Cleanup");
|
||||
println!("─────────────────────────────────────────────────────────────\n");
|
||||
|
||||
client.stop_runner("admin_secret", "osiris_runner", false).await?;
|
||||
println!("✅ Runner stopped");
|
||||
|
||||
client.remove_runner("admin_secret", "osiris_runner").await?;
|
||||
println!("✅ Runner removed");
|
||||
|
||||
supervisor.kill()?;
|
||||
println!("✅ Supervisor stopped");
|
||||
|
||||
println!("\n✨ Example completed successfully!");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
20
examples/osiris_openrpc/note.rhai
Normal file
20
examples/osiris_openrpc/note.rhai
Normal file
@@ -0,0 +1,20 @@
|
||||
print("Creating context for [alice, bob]...");
|
||||
let ctx = get_context(["alice", "bob"]);
|
||||
print("✓ Context ID: " + ctx.context_id());
|
||||
|
||||
print("\nCreating note...");
|
||||
let note = note("notes")
|
||||
.title("Sprint Planning Meeting")
|
||||
.content("Discussed Q1 2025 roadmap and milestones")
|
||||
.tag("sprint", "2025-Q1")
|
||||
.tag("team", "engineering")
|
||||
.tag("priority", "high")
|
||||
.mime("text/markdown");
|
||||
|
||||
print("✓ Note created");
|
||||
|
||||
print("\nStoring note in context...");
|
||||
ctx.save(note);
|
||||
print("✓ Note stored");
|
||||
|
||||
"Note 'Sprint Planning Meeting' created and stored successfully"
|
||||
21
examples/osiris_openrpc/query.rhai
Normal file
21
examples/osiris_openrpc/query.rhai
Normal file
@@ -0,0 +1,21 @@
|
||||
print("Querying context [alice, bob]...");
|
||||
let ctx = get_context(["alice", "bob"]);
|
||||
print("✓ Context ID: " + ctx.context_id());
|
||||
|
||||
print("\nListing all notes...");
|
||||
let notes = ctx.list("notes");
|
||||
print("✓ Found " + notes.len() + " note(s)");
|
||||
|
||||
print("\nRetrieving specific note...");
|
||||
let note = ctx.get("notes", "sprint_planning_001");
|
||||
print("✓ Retrieved note: sprint_planning_001");
|
||||
|
||||
print("\nQuerying context [alice, bob, charlie]...");
|
||||
let ctx2 = get_context(["alice", "bob", "charlie"]);
|
||||
print("✓ Context ID: " + ctx2.context_id());
|
||||
|
||||
print("\nListing all events...");
|
||||
let events = ctx2.list("events");
|
||||
print("✓ Found " + events.len() + " event(s)");
|
||||
|
||||
"Query complete: Found " + notes.len() + " notes and " + events.len() + " events"
|
||||
Reference in New Issue
Block a user