Compare commits
7 Commits
developmen
...
network_se
Author | SHA1 | Date | |
---|---|---|---|
|
da3da0ae30 | ||
|
784f87db97 | ||
|
773db2238d | ||
|
e8a369e3a2 | ||
|
4b4f3371b0 | ||
|
1bb731711b | ||
|
af89ef0149 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -63,7 +63,4 @@ sidebars.ts
|
|||||||
|
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
Cargo.toml.bak
|
Cargo.toml.bak
|
||||||
for_augment
|
for_augment
|
||||||
|
|
||||||
myenv.sh
|
|
||||||
|
|
@@ -16,7 +16,6 @@ members = [
|
|||||||
"packages/clients/postgresclient",
|
"packages/clients/postgresclient",
|
||||||
"packages/clients/redisclient",
|
"packages/clients/redisclient",
|
||||||
"packages/clients/zinitclient",
|
"packages/clients/zinitclient",
|
||||||
"packages/clients/rfsclient",
|
|
||||||
"packages/core/net",
|
"packages/core/net",
|
||||||
"packages/core/text",
|
"packages/core/text",
|
||||||
"packages/crypt/vault",
|
"packages/crypt/vault",
|
||||||
@@ -32,7 +31,6 @@ members = [
|
|||||||
"rhailib",
|
"rhailib",
|
||||||
"herodo",
|
"herodo",
|
||||||
"packages/clients/hetznerclient",
|
"packages/clients/hetznerclient",
|
||||||
"packages/ai/codemonkey",
|
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
@@ -44,7 +42,6 @@ rust-version = "1.70.0"
|
|||||||
# Core shared dependencies with consistent versions
|
# Core shared dependencies with consistent versions
|
||||||
anyhow = "1.0.98"
|
anyhow = "1.0.98"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bytes = "1.7.1"
|
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
env_logger = "0.11.8"
|
env_logger = "0.11.8"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
@@ -111,7 +108,6 @@ sal-kubernetes = { path = "packages/system/kubernetes" }
|
|||||||
sal-redisclient = { path = "packages/clients/redisclient" }
|
sal-redisclient = { path = "packages/clients/redisclient" }
|
||||||
sal-mycelium = { path = "packages/clients/myceliumclient" }
|
sal-mycelium = { path = "packages/clients/myceliumclient" }
|
||||||
sal-hetzner = { path = "packages/clients/hetznerclient" }
|
sal-hetzner = { path = "packages/clients/hetznerclient" }
|
||||||
sal-rfs-client = { path = "packages/clients/rfsclient" }
|
|
||||||
sal-text = { path = "packages/core/text" }
|
sal-text = { path = "packages/core/text" }
|
||||||
sal-os = { path = "packages/system/os" }
|
sal-os = { path = "packages/system/os" }
|
||||||
sal-net = { path = "packages/core/net" }
|
sal-net = { path = "packages/core/net" }
|
||||||
@@ -133,7 +129,6 @@ sal-kubernetes = { workspace = true, optional = true }
|
|||||||
sal-redisclient = { workspace = true, optional = true }
|
sal-redisclient = { workspace = true, optional = true }
|
||||||
sal-mycelium = { workspace = true, optional = true }
|
sal-mycelium = { workspace = true, optional = true }
|
||||||
sal-hetzner = { workspace = true, optional = true }
|
sal-hetzner = { workspace = true, optional = true }
|
||||||
sal-rfs-client = { workspace = true, optional = true }
|
|
||||||
sal-text = { workspace = true, optional = true }
|
sal-text = { workspace = true, optional = true }
|
||||||
sal-os = { workspace = true, optional = true }
|
sal-os = { workspace = true, optional = true }
|
||||||
sal-net = { workspace = true, optional = true }
|
sal-net = { workspace = true, optional = true }
|
||||||
@@ -154,7 +149,6 @@ kubernetes = ["dep:sal-kubernetes"]
|
|||||||
redisclient = ["dep:sal-redisclient"]
|
redisclient = ["dep:sal-redisclient"]
|
||||||
mycelium = ["dep:sal-mycelium"]
|
mycelium = ["dep:sal-mycelium"]
|
||||||
hetzner = ["dep:sal-hetzner"]
|
hetzner = ["dep:sal-hetzner"]
|
||||||
rfsclient = ["dep:sal-rfs-client"]
|
|
||||||
text = ["dep:sal-text"]
|
text = ["dep:sal-text"]
|
||||||
os = ["dep:sal-os"]
|
os = ["dep:sal-os"]
|
||||||
net = ["dep:sal-net"]
|
net = ["dep:sal-net"]
|
||||||
@@ -168,7 +162,7 @@ rhai = ["dep:sal-rhai"]
|
|||||||
|
|
||||||
# Convenience feature groups
|
# Convenience feature groups
|
||||||
core = ["os", "process", "text", "net"]
|
core = ["os", "process", "text", "net"]
|
||||||
clients = ["redisclient", "postgresclient", "zinit_client", "mycelium", "hetzner", "rfsclient"]
|
clients = ["redisclient", "postgresclient", "zinit_client", "mycelium", "hetzner"]
|
||||||
infrastructure = ["git", "vault", "kubernetes", "virt"]
|
infrastructure = ["git", "vault", "kubernetes", "virt"]
|
||||||
scripting = ["rhai"]
|
scripting = ["rhai"]
|
||||||
all = [
|
all = [
|
||||||
@@ -177,7 +171,6 @@ all = [
|
|||||||
"redisclient",
|
"redisclient",
|
||||||
"mycelium",
|
"mycelium",
|
||||||
"hetzner",
|
"hetzner",
|
||||||
"rfsclient",
|
|
||||||
"text",
|
"text",
|
||||||
"os",
|
"os",
|
||||||
"net",
|
"net",
|
||||||
|
43
_archive/service_manager/Cargo.toml
Normal file
43
_archive/service_manager/Cargo.toml
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[package]
|
||||||
|
name = "sal-service-manager"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
authors = ["PlanetFirst <info@incubaid.com>"]
|
||||||
|
description = "SAL Service Manager - Cross-platform service management for dynamic worker deployment"
|
||||||
|
repository = "https://git.threefold.info/herocode/sal"
|
||||||
|
license = "Apache-2.0"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Use workspace dependencies for consistency
|
||||||
|
thiserror = "1.0"
|
||||||
|
tokio = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
futures = { workspace = true }
|
||||||
|
once_cell = { workspace = true }
|
||||||
|
# Use base zinit-client instead of SAL wrapper
|
||||||
|
zinit-client = { version = "0.4.0" }
|
||||||
|
# Optional Rhai integration
|
||||||
|
rhai = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
|
||||||
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
|
# macOS-specific dependencies for launchctl
|
||||||
|
plist = "1.6"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["zinit"]
|
||||||
|
zinit = []
|
||||||
|
rhai = ["dep:rhai"]
|
||||||
|
|
||||||
|
# Enable zinit feature for tests
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio-test = "0.4"
|
||||||
|
rhai = { workspace = true }
|
||||||
|
tempfile = { workspace = true }
|
||||||
|
env_logger = "0.10"
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "zinit_integration_tests"
|
||||||
|
required-features = ["zinit"]
|
198
_archive/service_manager/README.md
Normal file
198
_archive/service_manager/README.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# SAL Service Manager
|
||||||
|
|
||||||
|
[](https://crates.io/crates/sal-service-manager)
|
||||||
|
[](https://docs.rs/sal-service-manager)
|
||||||
|
|
||||||
|
A cross-platform service management library for the System Abstraction Layer (SAL). This crate provides a unified interface for managing system services across different platforms, enabling dynamic deployment of workers and services.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Cross-platform service management** - Unified API across macOS and Linux
|
||||||
|
- **Dynamic worker deployment** - Perfect for circle workers and on-demand services
|
||||||
|
- **Platform-specific implementations**:
|
||||||
|
- **macOS**: Uses `launchctl` with plist management
|
||||||
|
- **Linux**: Uses `zinit` for lightweight service management (systemd also available)
|
||||||
|
- **Complete lifecycle management** - Start, stop, restart, status monitoring, and log retrieval
|
||||||
|
- **Service configuration** - Environment variables, working directories, auto-restart
|
||||||
|
- **Production-ready** - Comprehensive error handling and resource management
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Add this to your `Cargo.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
sal-service-manager = "0.1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or use it as part of the SAL ecosystem:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[dependencies]
|
||||||
|
sal = { version = "0.1.0", features = ["service_manager"] }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Primary Use Case: Dynamic Circle Worker Management
|
||||||
|
|
||||||
|
This service manager was designed specifically for dynamic deployment of circle workers in freezone environments. When a new resident registers, you can instantly launch a dedicated circle worker:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
// New resident registration triggers worker creation
|
||||||
|
fn deploy_circle_worker(resident_id: &str) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let manager = create_service_manager();
|
||||||
|
|
||||||
|
let mut env = HashMap::new();
|
||||||
|
env.insert("RESIDENT_ID".to_string(), resident_id.to_string());
|
||||||
|
env.insert("WORKER_TYPE".to_string(), "circle".to_string());
|
||||||
|
|
||||||
|
let config = ServiceConfig {
|
||||||
|
name: format!("circle-worker-{}", resident_id),
|
||||||
|
binary_path: "/usr/bin/circle-worker".to_string(),
|
||||||
|
args: vec!["--resident".to_string(), resident_id.to_string()],
|
||||||
|
working_directory: Some("/var/lib/circle-workers".to_string()),
|
||||||
|
environment: env,
|
||||||
|
auto_restart: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Deploy the worker
|
||||||
|
manager.start(&config)?;
|
||||||
|
println!("✅ Circle worker deployed for resident: {}", resident_id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Usage Example
|
||||||
|
|
||||||
|
Here is an example of the core service management API:
|
||||||
|
|
||||||
|
```rust,no_run
|
||||||
|
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let service_manager = create_service_manager();
|
||||||
|
|
||||||
|
let config = ServiceConfig {
|
||||||
|
name: "my-service".to_string(),
|
||||||
|
binary_path: "/usr/local/bin/my-service-executable".to_string(),
|
||||||
|
args: vec!["--config".to_string(), "/etc/my-service.conf".to_string()],
|
||||||
|
working_directory: Some("/var/tmp".to_string()),
|
||||||
|
environment: HashMap::new(),
|
||||||
|
auto_restart: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start a new service
|
||||||
|
service_manager.start(&config)?;
|
||||||
|
|
||||||
|
// Get the status of the service
|
||||||
|
let status = service_manager.status("my-service")?;
|
||||||
|
println!("Service status: {:?}", status);
|
||||||
|
|
||||||
|
// Stop the service
|
||||||
|
service_manager.stop("my-service")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
Comprehensive examples are available in the SAL examples directory:
|
||||||
|
|
||||||
|
### Circle Worker Manager Example
|
||||||
|
|
||||||
|
The primary use case - dynamically launching circle workers for new freezone residents:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the circle worker management example
|
||||||
|
herodo examples/service_manager/circle_worker_manager.rhai
|
||||||
|
```
|
||||||
|
|
||||||
|
This example demonstrates:
|
||||||
|
- Creating service configurations for circle workers
|
||||||
|
- Complete service lifecycle management
|
||||||
|
- Error handling and status monitoring
|
||||||
|
- Service cleanup and removal
|
||||||
|
|
||||||
|
### Basic Usage Example
|
||||||
|
|
||||||
|
A simpler example showing the core API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Run the basic usage example
|
||||||
|
herodo examples/service_manager/basic_usage.rhai
|
||||||
|
```
|
||||||
|
|
||||||
|
See `examples/service_manager/README.md` for detailed documentation.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run the test suite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test -p sal-service-manager
|
||||||
|
```
|
||||||
|
|
||||||
|
For Rhai integration tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test -p sal-service-manager --features rhai
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing with Herodo
|
||||||
|
|
||||||
|
To test the service manager with real Rhai scripts using herodo, first build herodo:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./build_herodo.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run Rhai scripts that use the service manager:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
herodo your_service_script.rhai
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Linux (zinit/systemd)
|
||||||
|
|
||||||
|
The service manager automatically discovers running zinit servers and falls back to systemd if none are found.
|
||||||
|
|
||||||
|
**For zinit (recommended):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Start zinit with default socket
|
||||||
|
zinit -s /tmp/zinit.sock init
|
||||||
|
|
||||||
|
# Or with a custom socket path
|
||||||
|
zinit -s /var/run/zinit.sock init
|
||||||
|
```
|
||||||
|
|
||||||
|
**Socket Discovery:**
|
||||||
|
The service manager will automatically find running zinit servers by checking:
|
||||||
|
1. `ZINIT_SOCKET_PATH` environment variable (if set)
|
||||||
|
2. Common socket locations: `/var/run/zinit.sock`, `/tmp/zinit.sock`, `/run/zinit.sock`, `./zinit.sock`
|
||||||
|
|
||||||
|
**Custom socket path:**
|
||||||
|
```bash
|
||||||
|
# Set custom socket path
|
||||||
|
export ZINIT_SOCKET_PATH=/your/custom/path/zinit.sock
|
||||||
|
```
|
||||||
|
|
||||||
|
**Systemd fallback:**
|
||||||
|
If no zinit server is detected, the service manager automatically falls back to systemd.
|
||||||
|
|
||||||
|
### macOS (launchctl)
|
||||||
|
|
||||||
|
No additional setup required - uses the built-in launchctl system.
|
||||||
|
|
||||||
|
## Platform Support
|
||||||
|
|
||||||
|
- **macOS**: Full support using `launchctl` for service management
|
||||||
|
- **Linux**: Full support using `zinit` for service management (systemd also available as alternative)
|
||||||
|
- **Windows**: Not currently supported
|
47
_archive/service_manager/examples/README.md
Normal file
47
_archive/service_manager/examples/README.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Service Manager Examples
|
||||||
|
|
||||||
|
This directory contains examples demonstrating the usage of the `sal-service-manager` crate.
|
||||||
|
|
||||||
|
## Running Examples
|
||||||
|
|
||||||
|
To run any example, use the following command structure from the `service_manager` crate's root directory:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cargo run --example <EXAMPLE_NAME>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 1. `simple_service`
|
||||||
|
|
||||||
|
This example demonstrates the ideal, clean lifecycle of a service using the separated `create` and `start` steps.
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
1. Creates a new service definition.
|
||||||
|
2. Starts the newly created service.
|
||||||
|
3. Checks its status to confirm it's running.
|
||||||
|
4. Stops the service.
|
||||||
|
5. Checks its status again to confirm it's stopped.
|
||||||
|
6. Removes the service definition.
|
||||||
|
|
||||||
|
**Run it:**
|
||||||
|
```sh
|
||||||
|
cargo run --example simple_service
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `service_spaghetti`
|
||||||
|
|
||||||
|
This example demonstrates how the service manager handles "messy" or improper sequences of operations, showcasing its error handling and robustness.
|
||||||
|
|
||||||
|
**Behavior:**
|
||||||
|
1. Creates a service.
|
||||||
|
2. Starts the service.
|
||||||
|
3. Tries to start the **same service again** (which should fail as it's already running).
|
||||||
|
4. Removes the service **without stopping it first** (the manager should handle this gracefully).
|
||||||
|
5. Tries to stop the **already removed** service (which should fail).
|
||||||
|
6. Tries to remove the service **again** (which should also fail).
|
||||||
|
|
||||||
|
**Run it:**
|
||||||
|
```sh
|
||||||
|
cargo run --example service_spaghetti
|
||||||
|
```
|
109
_archive/service_manager/examples/service_spaghetti.rs
Normal file
109
_archive/service_manager/examples/service_spaghetti.rs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
//! service_spaghetti - An example of messy service management.
|
||||||
|
//!
|
||||||
|
//! This example demonstrates how the service manager behaves when commands
|
||||||
|
//! are issued in a less-than-ideal order, such as starting a service that's
|
||||||
|
//! already running or removing a service that hasn't been stopped.
|
||||||
|
|
||||||
|
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize logging to see socket discovery in action
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let manager = match create_service_manager() {
|
||||||
|
Ok(manager) => manager,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: Failed to create service manager: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let service_name = "com.herocode.examples.spaghetti";
|
||||||
|
|
||||||
|
let service_config = ServiceConfig {
|
||||||
|
name: service_name.to_string(),
|
||||||
|
binary_path: "/bin/sh".to_string(),
|
||||||
|
args: vec![
|
||||||
|
"-c".to_string(),
|
||||||
|
"while true; do echo 'Spaghetti service is running...'; sleep 5; done".to_string(),
|
||||||
|
],
|
||||||
|
working_directory: None,
|
||||||
|
environment: HashMap::new(),
|
||||||
|
auto_restart: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("--- Service Spaghetti Example ---");
|
||||||
|
println!("This example demonstrates messy, error-prone service management.");
|
||||||
|
|
||||||
|
// Cleanup from previous runs to ensure a clean slate
|
||||||
|
if let Ok(true) = manager.exists(service_name) {
|
||||||
|
println!(
|
||||||
|
"\nService '{}' found from a previous run. Cleaning up first.",
|
||||||
|
service_name
|
||||||
|
);
|
||||||
|
let _ = manager.stop(service_name);
|
||||||
|
let _ = manager.remove(service_name);
|
||||||
|
println!("Cleanup complete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Start the service (creates and starts in one step)
|
||||||
|
println!("\n1. Starting the service for the first time...");
|
||||||
|
match manager.start(&service_config) {
|
||||||
|
Ok(()) => println!(" -> Success: Service '{}' started.", service_name),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!(
|
||||||
|
" -> Error: Failed to start service: {}. Halting example.",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thread::sleep(Duration::from_secs(2));
|
||||||
|
|
||||||
|
// 2. Try to start the service again while it's already running
|
||||||
|
println!("\n2. Trying to start the *same service* again...");
|
||||||
|
match manager.start(&service_config) {
|
||||||
|
Ok(()) => println!(" -> Unexpected Success: Service started again."),
|
||||||
|
Err(e) => eprintln!(
|
||||||
|
" -> Expected Error: {}. The manager should detect it is already running.",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Let it run for a bit
|
||||||
|
println!("\n3. Letting the service run for 5 seconds...");
|
||||||
|
thread::sleep(Duration::from_secs(5));
|
||||||
|
|
||||||
|
// 4. Remove the service without stopping it first
|
||||||
|
// The `remove` function is designed to stop the service if it's running.
|
||||||
|
println!("\n4. Removing the service without explicitly stopping it first...");
|
||||||
|
match manager.remove(service_name) {
|
||||||
|
Ok(()) => println!(" -> Success: Service was stopped and removed."),
|
||||||
|
Err(e) => eprintln!(" -> Error: Failed to remove service: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Try to stop the service after it has been removed
|
||||||
|
println!("\n5. Trying to stop the service that was just removed...");
|
||||||
|
match manager.stop(service_name) {
|
||||||
|
Ok(()) => println!(" -> Unexpected Success: Stopped a removed service."),
|
||||||
|
Err(e) => eprintln!(
|
||||||
|
" -> Expected Error: {}. The manager knows the service is gone.",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Try to remove the service again
|
||||||
|
println!("\n6. Trying to remove the service again...");
|
||||||
|
match manager.remove(service_name) {
|
||||||
|
Ok(()) => println!(" -> Unexpected Success: Removed a non-existent service."),
|
||||||
|
Err(e) => eprintln!(
|
||||||
|
" -> Expected Error: {}. The manager correctly reports it's not found.",
|
||||||
|
e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- Spaghetti Example Finished ---");
|
||||||
|
}
|
110
_archive/service_manager/examples/simple_service.rs
Normal file
110
_archive/service_manager/examples/simple_service.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use sal_service_manager::{create_service_manager, ServiceConfig};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize logging to see socket discovery in action
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
// 1. Create a service manager for the current platform
|
||||||
|
let manager = match create_service_manager() {
|
||||||
|
Ok(manager) => manager,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: Failed to create service manager: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2. Define the configuration for our new service
|
||||||
|
let service_name = "com.herocode.examples.simpleservice";
|
||||||
|
let service_config = ServiceConfig {
|
||||||
|
name: service_name.to_string(),
|
||||||
|
// A simple command that runs in a loop
|
||||||
|
binary_path: "/bin/sh".to_string(),
|
||||||
|
args: vec![
|
||||||
|
"-c".to_string(),
|
||||||
|
"while true; do echo 'Simple service is running...'; date; sleep 5; done".to_string(),
|
||||||
|
],
|
||||||
|
working_directory: None,
|
||||||
|
environment: HashMap::new(),
|
||||||
|
auto_restart: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("--- Service Manager Example ---");
|
||||||
|
|
||||||
|
// Cleanup from previous runs, if necessary
|
||||||
|
if let Ok(true) = manager.exists(service_name) {
|
||||||
|
println!(
|
||||||
|
"Service '{}' already exists. Cleaning up before starting.",
|
||||||
|
service_name
|
||||||
|
);
|
||||||
|
if let Err(e) = manager.stop(service_name) {
|
||||||
|
println!(
|
||||||
|
"Note: could not stop existing service (it might not be running): {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if let Err(e) = manager.remove(service_name) {
|
||||||
|
eprintln!("Error: failed to remove existing service: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
println!("Cleanup complete.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Start the service (creates and starts in one step)
|
||||||
|
println!("\n1. Starting service: '{}'", service_name);
|
||||||
|
match manager.start(&service_config) {
|
||||||
|
Ok(()) => println!("Service '{}' started successfully.", service_name),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error: Failed to start service '{}': {}", service_name, e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give it a moment to run
|
||||||
|
println!("\nWaiting for 2 seconds for the service to initialize...");
|
||||||
|
thread::sleep(Duration::from_secs(2));
|
||||||
|
|
||||||
|
// 4. Check the status of the service
|
||||||
|
println!("\n2. Checking service status...");
|
||||||
|
match manager.status(service_name) {
|
||||||
|
Ok(status) => println!("Service status: {:?}", status),
|
||||||
|
Err(e) => eprintln!(
|
||||||
|
"Error: Failed to get status for service '{}': {}",
|
||||||
|
service_name, e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nLetting the service run for 10 seconds. Check logs if you can.");
|
||||||
|
thread::sleep(Duration::from_secs(10));
|
||||||
|
|
||||||
|
// 5. Stop the service
|
||||||
|
println!("\n3. Stopping service: '{}'", service_name);
|
||||||
|
match manager.stop(service_name) {
|
||||||
|
Ok(()) => println!("Service '{}' stopped successfully.", service_name),
|
||||||
|
Err(e) => eprintln!("Error: Failed to stop service '{}': {}", service_name, e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\nWaiting for 2 seconds for the service to stop...");
|
||||||
|
thread::sleep(Duration::from_secs(2));
|
||||||
|
|
||||||
|
// Check status again
|
||||||
|
println!("\n4. Checking status after stopping...");
|
||||||
|
match manager.status(service_name) {
|
||||||
|
Ok(status) => println!("Service status: {:?}", status),
|
||||||
|
Err(e) => eprintln!(
|
||||||
|
"Error: Failed to get status for service '{}': {}",
|
||||||
|
service_name, e
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. Remove the service
|
||||||
|
println!("\n5. Removing service: '{}'", service_name);
|
||||||
|
match manager.remove(service_name) {
|
||||||
|
Ok(()) => println!("Service '{}' removed successfully.", service_name),
|
||||||
|
Err(e) => eprintln!("Error: Failed to remove service '{}': {}", service_name, e),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("\n--- Example Finished ---");
|
||||||
|
}
|
47
_archive/service_manager/examples/socket_discovery_test.rs
Normal file
47
_archive/service_manager/examples/socket_discovery_test.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
//! Socket Discovery Test
|
||||||
|
//!
|
||||||
|
//! This example demonstrates the zinit socket discovery functionality.
|
||||||
|
//! It shows how the service manager finds available zinit sockets.
|
||||||
|
|
||||||
|
use sal_service_manager::create_service_manager;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
// Initialize logging to see socket discovery in action
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
println!("=== Zinit Socket Discovery Test ===");
|
||||||
|
println!("This test demonstrates how the service manager discovers zinit sockets.");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Test environment variable
|
||||||
|
if let Ok(socket_path) = std::env::var("ZINIT_SOCKET_PATH") {
|
||||||
|
println!("🔍 ZINIT_SOCKET_PATH environment variable set to: {}", socket_path);
|
||||||
|
} else {
|
||||||
|
println!("🔍 ZINIT_SOCKET_PATH environment variable not set");
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
|
||||||
|
println!("🚀 Creating service manager...");
|
||||||
|
match create_service_manager() {
|
||||||
|
Ok(_manager) => {
|
||||||
|
println!("✅ Service manager created successfully!");
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
println!("📱 Platform: macOS - Using launchctl");
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
println!("🐧 Platform: Linux - Check logs above for socket discovery details");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("❌ Failed to create service manager: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!();
|
||||||
|
println!("=== Test Complete ===");
|
||||||
|
println!();
|
||||||
|
println!("To test zinit socket discovery on Linux:");
|
||||||
|
println!("1. Start zinit: zinit -s /tmp/zinit.sock init");
|
||||||
|
println!("2. Run with logging: RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager");
|
||||||
|
println!("3. Or set custom path: ZINIT_SOCKET_PATH=/custom/path.sock RUST_LOG=debug cargo run --example socket_discovery_test -p sal-service-manager");
|
||||||
|
}
|
492
_archive/service_manager/src/launchctl.rs
Normal file
492
_archive/service_manager/src/launchctl.rs
Normal file
@@ -0,0 +1,492 @@
|
|||||||
|
use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use tokio::process::Command;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
|
||||||
|
// Shared runtime for async operations - production-safe initialization
|
||||||
|
static ASYNC_RUNTIME: Lazy<Option<Runtime>> = Lazy::new(|| Runtime::new().ok());
|
||||||
|
|
||||||
|
/// Get the async runtime, creating a temporary one if the static runtime failed
|
||||||
|
fn get_runtime() -> Result<Runtime, ServiceManagerError> {
|
||||||
|
// Try to use the static runtime first
|
||||||
|
if let Some(_runtime) = ASYNC_RUNTIME.as_ref() {
|
||||||
|
// We can't return a reference to the static runtime because we need ownership
|
||||||
|
// for block_on, so we create a new one. This is a reasonable trade-off for safety.
|
||||||
|
Runtime::new().map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Static runtime failed, try to create a new one
|
||||||
|
Runtime::new().map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct LaunchctlServiceManager {
|
||||||
|
service_prefix: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct LaunchDaemon {
|
||||||
|
#[serde(rename = "Label")]
|
||||||
|
label: String,
|
||||||
|
#[serde(rename = "ProgramArguments")]
|
||||||
|
program_arguments: Vec<String>,
|
||||||
|
#[serde(rename = "WorkingDirectory", skip_serializing_if = "Option::is_none")]
|
||||||
|
working_directory: Option<String>,
|
||||||
|
#[serde(
|
||||||
|
rename = "EnvironmentVariables",
|
||||||
|
skip_serializing_if = "Option::is_none"
|
||||||
|
)]
|
||||||
|
environment_variables: Option<HashMap<String, String>>,
|
||||||
|
#[serde(rename = "KeepAlive", skip_serializing_if = "Option::is_none")]
|
||||||
|
keep_alive: Option<bool>,
|
||||||
|
#[serde(rename = "RunAtLoad")]
|
||||||
|
run_at_load: bool,
|
||||||
|
#[serde(rename = "StandardOutPath", skip_serializing_if = "Option::is_none")]
|
||||||
|
standard_out_path: Option<String>,
|
||||||
|
#[serde(rename = "StandardErrorPath", skip_serializing_if = "Option::is_none")]
|
||||||
|
standard_error_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LaunchctlServiceManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
service_prefix: "tf.ourworld.circles".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_service_label(&self, service_name: &str) -> String {
|
||||||
|
format!("{}.{}", self.service_prefix, service_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_plist_path(&self, service_name: &str) -> PathBuf {
|
||||||
|
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||||
|
PathBuf::from(home)
|
||||||
|
.join("Library")
|
||||||
|
.join("LaunchAgents")
|
||||||
|
.join(format!("{}.plist", self.get_service_label(service_name)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_log_path(&self, service_name: &str) -> PathBuf {
|
||||||
|
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||||
|
PathBuf::from(home)
|
||||||
|
.join("Library")
|
||||||
|
.join("Logs")
|
||||||
|
.join("circles")
|
||||||
|
.join(format!("{}.log", service_name))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn create_plist(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||||
|
let label = self.get_service_label(&config.name);
|
||||||
|
let plist_path = self.get_plist_path(&config.name);
|
||||||
|
let log_path = self.get_log_path(&config.name);
|
||||||
|
|
||||||
|
// Ensure the LaunchAgents directory exists
|
||||||
|
if let Some(parent) = plist_path.parent() {
|
||||||
|
tokio::fs::create_dir_all(parent).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the logs directory exists
|
||||||
|
if let Some(parent) = log_path.parent() {
|
||||||
|
tokio::fs::create_dir_all(parent).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut program_arguments = vec![config.binary_path.clone()];
|
||||||
|
program_arguments.extend(config.args.clone());
|
||||||
|
|
||||||
|
let launch_daemon = LaunchDaemon {
|
||||||
|
label: label.clone(),
|
||||||
|
program_arguments,
|
||||||
|
working_directory: config.working_directory.clone(),
|
||||||
|
environment_variables: if config.environment.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(config.environment.clone())
|
||||||
|
},
|
||||||
|
keep_alive: if config.auto_restart {
|
||||||
|
Some(true)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
run_at_load: true,
|
||||||
|
standard_out_path: Some(log_path.to_string_lossy().to_string()),
|
||||||
|
standard_error_path: Some(log_path.to_string_lossy().to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut plist_content = Vec::new();
|
||||||
|
plist::to_writer_xml(&mut plist_content, &launch_daemon)
|
||||||
|
.map_err(|e| ServiceManagerError::Other(format!("Failed to serialize plist: {}", e)))?;
|
||||||
|
let plist_content = String::from_utf8(plist_content).map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to convert plist to string: {}", e))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
tokio::fs::write(&plist_path, plist_content).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn run_launchctl(&self, args: &[&str]) -> Result<String, ServiceManagerError> {
|
||||||
|
let output = Command::new("launchctl").args(args).output().await?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(ServiceManagerError::Other(format!(
|
||||||
|
"launchctl command failed: {}",
|
||||||
|
stderr
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_service_status(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError> {
|
||||||
|
use tokio::time::{sleep, timeout, Duration};
|
||||||
|
|
||||||
|
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||||
|
let poll_interval = Duration::from_millis(500);
|
||||||
|
|
||||||
|
let result = timeout(timeout_duration, async {
|
||||||
|
loop {
|
||||||
|
match self.status(service_name) {
|
||||||
|
Ok(ServiceStatus::Running) => {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
Ok(ServiceStatus::Failed) => {
|
||||||
|
// Service failed, get error details from logs
|
||||||
|
let logs = self.logs(service_name, Some(20)).unwrap_or_default();
|
||||||
|
let error_msg = if logs.is_empty() {
|
||||||
|
"Service failed to start (no logs available)".to_string()
|
||||||
|
} else {
|
||||||
|
// Extract error lines from logs
|
||||||
|
let error_lines: Vec<&str> = logs
|
||||||
|
.lines()
|
||||||
|
.filter(|line| {
|
||||||
|
line.to_lowercase().contains("error")
|
||||||
|
|| line.to_lowercase().contains("failed")
|
||||||
|
})
|
||||||
|
.take(3)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if error_lines.is_empty() {
|
||||||
|
format!(
|
||||||
|
"Service failed to start. Recent logs:\n{}",
|
||||||
|
logs.lines()
|
||||||
|
.rev()
|
||||||
|
.take(5)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.into_iter()
|
||||||
|
.rev()
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join("\n")
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"Service failed to start. Errors:\n{}",
|
||||||
|
error_lines.join("\n")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Err(ServiceManagerError::StartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
error_msg,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(ServiceStatus::Stopped) | Ok(ServiceStatus::Unknown) => {
|
||||||
|
// Still starting, continue polling
|
||||||
|
sleep(poll_interval).await;
|
||||||
|
}
|
||||||
|
Err(ServiceManagerError::ServiceNotFound(_)) => {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(Ok(())) => Ok(()),
|
||||||
|
Ok(Err(e)) => Err(e),
|
||||||
|
Err(_) => Err(ServiceManagerError::StartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
format!("Service did not start within {} seconds", timeout_secs),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceManager for LaunchctlServiceManager {
|
||||||
|
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> {
|
||||||
|
let plist_path = self.get_plist_path(service_name);
|
||||||
|
Ok(plist_path.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||||
|
// Use production-safe runtime for async operations
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
let label = self.get_service_label(&config.name);
|
||||||
|
|
||||||
|
// Check if service is already loaded
|
||||||
|
let list_output = self.run_launchctl(&["list"]).await?;
|
||||||
|
if list_output.contains(&label) {
|
||||||
|
return Err(ServiceManagerError::ServiceAlreadyExists(
|
||||||
|
config.name.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the plist file
|
||||||
|
self.create_plist(config).await?;
|
||||||
|
|
||||||
|
// Load the service
|
||||||
|
let plist_path = self.get_plist_path(&config.name);
|
||||||
|
self.run_launchctl(&["load", &plist_path.to_string_lossy()])
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ServiceManagerError::StartFailed(config.name.clone(), e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
let label = self.get_service_label(service_name);
|
||||||
|
let plist_path = self.get_plist_path(service_name);
|
||||||
|
|
||||||
|
// Check if plist file exists
|
||||||
|
if !plist_path.exists() {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if service is already loaded and running
|
||||||
|
let list_output = self.run_launchctl(&["list"]).await?;
|
||||||
|
if list_output.contains(&label) {
|
||||||
|
// Service is loaded, check if it's running
|
||||||
|
match self.status(service_name)? {
|
||||||
|
ServiceStatus::Running => {
|
||||||
|
return Ok(()); // Already running, nothing to do
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Service is loaded but not running, try to start it
|
||||||
|
self.run_launchctl(&["start", &label]).await.map_err(|e| {
|
||||||
|
ServiceManagerError::StartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
e.to_string(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service is not loaded, load it
|
||||||
|
self.run_launchctl(&["load", &plist_path.to_string_lossy()])
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ServiceManagerError::StartFailed(service_name.to_string(), e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_and_confirm(
|
||||||
|
&self,
|
||||||
|
config: &ServiceConfig,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError> {
|
||||||
|
// First start the service
|
||||||
|
self.start(config)?;
|
||||||
|
|
||||||
|
// Then wait for confirmation using production-safe runtime
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
self.wait_for_service_status(&config.name, timeout_secs)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_existing_and_confirm(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError> {
|
||||||
|
// First start the existing service
|
||||||
|
self.start_existing(service_name)?;
|
||||||
|
|
||||||
|
// Then wait for confirmation using production-safe runtime
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
self.wait_for_service_status(service_name, timeout_secs)
|
||||||
|
.await
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
let _label = self.get_service_label(service_name);
|
||||||
|
let plist_path = self.get_plist_path(service_name);
|
||||||
|
|
||||||
|
// Unload the service
|
||||||
|
self.run_launchctl(&["unload", &plist_path.to_string_lossy()])
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
ServiceManagerError::StopFailed(service_name.to_string(), e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
// For launchctl, we stop and start
|
||||||
|
if let Err(e) = self.stop(service_name) {
|
||||||
|
// If stop fails because service doesn't exist, that's ok for restart
|
||||||
|
if !matches!(e, ServiceManagerError::ServiceNotFound(_)) {
|
||||||
|
return Err(ServiceManagerError::RestartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
e.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need the config to restart, but we don't have it stored
|
||||||
|
// For now, return an error - in a real implementation we might store configs
|
||||||
|
Err(ServiceManagerError::RestartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
"Restart requires re-providing service configuration".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> {
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
let label = self.get_service_label(service_name);
|
||||||
|
let plist_path = self.get_plist_path(service_name);
|
||||||
|
|
||||||
|
// First check if the plist file exists
|
||||||
|
if !plist_path.exists() {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let list_output = self.run_launchctl(&["list"]).await?;
|
||||||
|
|
||||||
|
if !list_output.contains(&label) {
|
||||||
|
return Ok(ServiceStatus::Stopped);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get detailed status
|
||||||
|
match self.run_launchctl(&["list", &label]).await {
|
||||||
|
Ok(output) => {
|
||||||
|
if output.contains("\"PID\" = ") {
|
||||||
|
Ok(ServiceStatus::Running)
|
||||||
|
} else if output.contains("\"LastExitStatus\" = ") {
|
||||||
|
Ok(ServiceStatus::Failed)
|
||||||
|
} else {
|
||||||
|
Ok(ServiceStatus::Unknown)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => Ok(ServiceStatus::Stopped),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logs(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
lines: Option<usize>,
|
||||||
|
) -> Result<String, ServiceManagerError> {
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
let log_path = self.get_log_path(service_name);
|
||||||
|
|
||||||
|
if !log_path.exists() {
|
||||||
|
return Ok(String::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
match lines {
|
||||||
|
Some(n) => {
|
||||||
|
let output = Command::new("tail")
|
||||||
|
.args(&["-n", &n.to_string(), &log_path.to_string_lossy()])
|
||||||
|
.output()
|
||||||
|
.await?;
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let content = tokio::fs::read_to_string(&log_path).await?;
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(&self) -> Result<Vec<String>, ServiceManagerError> {
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
let list_output = self.run_launchctl(&["list"]).await?;
|
||||||
|
|
||||||
|
let services: Vec<String> = list_output
|
||||||
|
.lines()
|
||||||
|
.filter_map(|line| {
|
||||||
|
if line.contains(&self.service_prefix) {
|
||||||
|
// Extract service name from label
|
||||||
|
line.split_whitespace()
|
||||||
|
.last()
|
||||||
|
.and_then(|label| {
|
||||||
|
label.strip_prefix(&format!("{}.", self.service_prefix))
|
||||||
|
})
|
||||||
|
.map(|s| s.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(services)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
// Try to stop the service first, but don't fail if it's already stopped or doesn't exist
|
||||||
|
if let Err(e) = self.stop(service_name) {
|
||||||
|
// Log the error but continue with removal
|
||||||
|
log::warn!(
|
||||||
|
"Failed to stop service '{}' before removal: {}",
|
||||||
|
service_name,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the plist file using production-safe runtime
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime.block_on(async {
|
||||||
|
let plist_path = self.get_plist_path(service_name);
|
||||||
|
if plist_path.exists() {
|
||||||
|
tokio::fs::remove_file(&plist_path).await?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
301
_archive/service_manager/src/lib.rs
Normal file
301
_archive/service_manager/src/lib.rs
Normal file
@@ -0,0 +1,301 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ServiceManagerError {
|
||||||
|
#[error("Service '{0}' not found")]
|
||||||
|
ServiceNotFound(String),
|
||||||
|
#[error("Service '{0}' already exists")]
|
||||||
|
ServiceAlreadyExists(String),
|
||||||
|
#[error("Failed to start service '{0}': {1}")]
|
||||||
|
StartFailed(String, String),
|
||||||
|
#[error("Failed to stop service '{0}': {1}")]
|
||||||
|
StopFailed(String, String),
|
||||||
|
#[error("Failed to restart service '{0}': {1}")]
|
||||||
|
RestartFailed(String, String),
|
||||||
|
#[error("Failed to get logs for service '{0}': {1}")]
|
||||||
|
LogsFailed(String, String),
|
||||||
|
#[error("IO error: {0}")]
|
||||||
|
IoError(#[from] std::io::Error),
|
||||||
|
#[error("Service manager error: {0}")]
|
||||||
|
Other(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ServiceConfig {
|
||||||
|
pub name: String,
|
||||||
|
pub binary_path: String,
|
||||||
|
pub args: Vec<String>,
|
||||||
|
pub working_directory: Option<String>,
|
||||||
|
pub environment: HashMap<String, String>,
|
||||||
|
pub auto_restart: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum ServiceStatus {
|
||||||
|
Running,
|
||||||
|
Stopped,
|
||||||
|
Failed,
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait ServiceManager: Send + Sync {
|
||||||
|
/// Check if a service exists
|
||||||
|
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Start a service with the given configuration
|
||||||
|
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Start an existing service by name (load existing plist/config)
|
||||||
|
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Start a service and wait for confirmation that it's running or failed
|
||||||
|
fn start_and_confirm(
|
||||||
|
&self,
|
||||||
|
config: &ServiceConfig,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Start an existing service and wait for confirmation that it's running or failed
|
||||||
|
fn start_existing_and_confirm(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Stop a service by name
|
||||||
|
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Restart a service by name
|
||||||
|
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Get the status of a service
|
||||||
|
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Get logs for a service
|
||||||
|
fn logs(&self, service_name: &str, lines: Option<usize>)
|
||||||
|
-> Result<String, ServiceManagerError>;
|
||||||
|
|
||||||
|
/// List all managed services
|
||||||
|
fn list(&self) -> Result<Vec<String>, ServiceManagerError>;
|
||||||
|
|
||||||
|
/// Remove a service configuration (stop if running)
|
||||||
|
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform-specific implementations
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
mod launchctl;
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
pub use launchctl::LaunchctlServiceManager;
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
mod systemd;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub use systemd::SystemdServiceManager;
|
||||||
|
|
||||||
|
mod zinit;
|
||||||
|
pub use zinit::ZinitServiceManager;
|
||||||
|
|
||||||
|
#[cfg(feature = "rhai")]
|
||||||
|
pub mod rhai;
|
||||||
|
|
||||||
|
/// Discover available zinit socket paths
|
||||||
|
///
|
||||||
|
/// This function checks for zinit sockets in the following order:
|
||||||
|
/// 1. Environment variable ZINIT_SOCKET_PATH (if set)
|
||||||
|
/// 2. Common socket locations with connectivity testing
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns the first working socket path found, or None if no working zinit server is detected.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn discover_zinit_socket() -> Option<String> {
|
||||||
|
// First check environment variable
|
||||||
|
if let Ok(env_socket_path) = std::env::var("ZINIT_SOCKET_PATH") {
|
||||||
|
log::debug!("Checking ZINIT_SOCKET_PATH: {}", env_socket_path);
|
||||||
|
if test_zinit_socket(&env_socket_path) {
|
||||||
|
log::info!(
|
||||||
|
"Using zinit socket from ZINIT_SOCKET_PATH: {}",
|
||||||
|
env_socket_path
|
||||||
|
);
|
||||||
|
return Some(env_socket_path);
|
||||||
|
} else {
|
||||||
|
log::warn!(
|
||||||
|
"ZINIT_SOCKET_PATH specified but socket is not accessible: {}",
|
||||||
|
env_socket_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try common socket locations
|
||||||
|
let common_paths = [
|
||||||
|
"/var/run/zinit.sock",
|
||||||
|
"/tmp/zinit.sock",
|
||||||
|
"/run/zinit.sock",
|
||||||
|
"./zinit.sock",
|
||||||
|
];
|
||||||
|
|
||||||
|
log::debug!("Discovering zinit socket from common locations...");
|
||||||
|
for path in &common_paths {
|
||||||
|
log::debug!("Testing socket path: {}", path);
|
||||||
|
if test_zinit_socket(path) {
|
||||||
|
log::info!("Found working zinit socket at: {}", path);
|
||||||
|
return Some(path.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log::debug!("No working zinit socket found");
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test if a zinit socket is accessible and responsive
|
||||||
|
///
|
||||||
|
/// This function attempts to create a ZinitServiceManager and perform a basic
|
||||||
|
/// connectivity test by listing services.
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
fn test_zinit_socket(socket_path: &str) -> bool {
|
||||||
|
// Check if socket file exists first
|
||||||
|
if !std::path::Path::new(socket_path).exists() {
|
||||||
|
log::debug!("Socket file does not exist: {}", socket_path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to create a manager and test basic connectivity
|
||||||
|
match ZinitServiceManager::new(socket_path) {
|
||||||
|
Ok(manager) => {
|
||||||
|
// Test basic connectivity by trying to list services
|
||||||
|
match manager.list() {
|
||||||
|
Ok(_) => {
|
||||||
|
log::debug!("Socket {} is responsive", socket_path);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("Socket {} exists but not responsive: {}", socket_path, e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("Failed to create manager for socket {}: {}", socket_path, e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a service manager appropriate for the current platform
|
||||||
|
///
|
||||||
|
/// - On macOS: Uses launchctl for service management
|
||||||
|
/// - On Linux: Uses zinit for service management with systemd fallback
|
||||||
|
///
|
||||||
|
/// # Returns
|
||||||
|
///
|
||||||
|
/// Returns a Result containing the service manager or an error if initialization fails.
|
||||||
|
/// On Linux, it first tries to discover a working zinit socket. If no zinit server is found,
|
||||||
|
/// it will fall back to systemd.
|
||||||
|
///
|
||||||
|
/// # Environment Variables
|
||||||
|
///
|
||||||
|
/// - `ZINIT_SOCKET_PATH`: Specifies the zinit socket path (Linux only)
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `ServiceManagerError` if:
|
||||||
|
/// - The platform is not supported (Windows, etc.)
|
||||||
|
/// - Service manager initialization fails on all available backends
|
||||||
|
pub fn create_service_manager() -> Result<Box<dyn ServiceManager>, ServiceManagerError> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
Ok(Box::new(LaunchctlServiceManager::new()))
|
||||||
|
}
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
// Try to discover a working zinit socket
|
||||||
|
if let Some(socket_path) = discover_zinit_socket() {
|
||||||
|
match ZinitServiceManager::new(&socket_path) {
|
||||||
|
Ok(zinit_manager) => {
|
||||||
|
log::info!("Using zinit service manager with socket: {}", socket_path);
|
||||||
|
return Ok(Box::new(zinit_manager));
|
||||||
|
}
|
||||||
|
Err(zinit_error) => {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to create zinit manager for discovered socket {}: {}",
|
||||||
|
socket_path,
|
||||||
|
zinit_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log::info!("No running zinit server detected. To use zinit, start it with: zinit -s /tmp/zinit.sock init");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to systemd
|
||||||
|
log::info!("Falling back to systemd service manager");
|
||||||
|
Ok(Box::new(SystemdServiceManager::new()))
|
||||||
|
}
|
||||||
|
#[cfg(not(any(target_os = "macos", target_os = "linux")))]
|
||||||
|
{
|
||||||
|
Err(ServiceManagerError::Other(
|
||||||
|
"Service manager not implemented for this platform".to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a service manager for zinit with a custom socket path
|
||||||
|
///
|
||||||
|
/// This is useful when zinit is running with a non-default socket path
|
||||||
|
pub fn create_zinit_service_manager(
|
||||||
|
socket_path: &str,
|
||||||
|
) -> Result<Box<dyn ServiceManager>, ServiceManagerError> {
|
||||||
|
Ok(Box::new(ZinitServiceManager::new(socket_path)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a service manager for systemd (Linux alternative)
|
||||||
|
///
|
||||||
|
/// This creates a systemd-based service manager as an alternative to zinit on Linux
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
pub fn create_systemd_service_manager() -> Box<dyn ServiceManager> {
|
||||||
|
Box::new(SystemdServiceManager::new())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_service_manager() {
|
||||||
|
// This test ensures the service manager can be created without panicking
|
||||||
|
let result = create_service_manager();
|
||||||
|
assert!(result.is_ok(), "Service manager creation should succeed");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_socket_discovery_with_env_var() {
|
||||||
|
// Test that environment variable is respected
|
||||||
|
std::env::set_var("ZINIT_SOCKET_PATH", "/test/path.sock");
|
||||||
|
|
||||||
|
// The discover function should check the env var first
|
||||||
|
// Since the socket doesn't exist, it should return None, but we can't test
|
||||||
|
// the actual discovery logic without a real socket
|
||||||
|
|
||||||
|
std::env::remove_var("ZINIT_SOCKET_PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_socket_discovery_without_env_var() {
|
||||||
|
// Ensure env var is not set
|
||||||
|
std::env::remove_var("ZINIT_SOCKET_PATH");
|
||||||
|
|
||||||
|
// The discover function should try common paths
|
||||||
|
// Since no zinit is running, it should return None
|
||||||
|
let result = discover_zinit_socket();
|
||||||
|
|
||||||
|
// This is expected to be None in test environment
|
||||||
|
assert!(
|
||||||
|
result.is_none(),
|
||||||
|
"Should return None when no zinit server is running"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
256
_archive/service_manager/src/rhai.rs
Normal file
256
_archive/service_manager/src/rhai.rs
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
//! Rhai integration for the service manager module
|
||||||
|
//!
|
||||||
|
//! This module provides Rhai scripting support for service management operations.
|
||||||
|
|
||||||
|
use crate::{create_service_manager, ServiceConfig, ServiceManager};
|
||||||
|
use rhai::{Engine, EvalAltResult, Map};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A wrapper around ServiceManager that can be used in Rhai
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RhaiServiceManager {
|
||||||
|
inner: Arc<Box<dyn ServiceManager>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RhaiServiceManager {
|
||||||
|
pub fn new() -> Result<Self, Box<EvalAltResult>> {
|
||||||
|
let manager = create_service_manager()
|
||||||
|
.map_err(|e| format!("Failed to create service manager: {}", e))?;
|
||||||
|
Ok(Self {
|
||||||
|
inner: Arc::new(manager),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the service manager module with a Rhai engine
|
||||||
|
pub fn register_service_manager_module(engine: &mut Engine) -> Result<(), Box<EvalAltResult>> {
|
||||||
|
// Factory function to create service manager
|
||||||
|
engine.register_type::<RhaiServiceManager>();
|
||||||
|
engine.register_fn(
|
||||||
|
"create_service_manager",
|
||||||
|
|| -> Result<RhaiServiceManager, Box<EvalAltResult>> { RhaiServiceManager::new() },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Service management functions
|
||||||
|
engine.register_fn(
|
||||||
|
"start",
|
||||||
|
|manager: &mut RhaiServiceManager, config: Map| -> Result<(), Box<EvalAltResult>> {
|
||||||
|
let service_config = map_to_service_config(config)?;
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.start(&service_config)
|
||||||
|
.map_err(|e| format!("Failed to start service: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"stop",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
service_name: String|
|
||||||
|
-> Result<(), Box<EvalAltResult>> {
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.stop(&service_name)
|
||||||
|
.map_err(|e| format!("Failed to stop service: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"restart",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
service_name: String|
|
||||||
|
-> Result<(), Box<EvalAltResult>> {
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.restart(&service_name)
|
||||||
|
.map_err(|e| format!("Failed to restart service: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"status",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
service_name: String|
|
||||||
|
-> Result<String, Box<EvalAltResult>> {
|
||||||
|
let status = manager
|
||||||
|
.inner
|
||||||
|
.status(&service_name)
|
||||||
|
.map_err(|e| format!("Failed to get service status: {}", e))?;
|
||||||
|
Ok(format!("{:?}", status))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"logs",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
service_name: String,
|
||||||
|
lines: i64|
|
||||||
|
-> Result<String, Box<EvalAltResult>> {
|
||||||
|
let lines_opt = if lines > 0 {
|
||||||
|
Some(lines as usize)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.logs(&service_name, lines_opt)
|
||||||
|
.map_err(|e| format!("Failed to get service logs: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"list",
|
||||||
|
|manager: &mut RhaiServiceManager| -> Result<Vec<String>, Box<EvalAltResult>> {
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.list()
|
||||||
|
.map_err(|e| format!("Failed to list services: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"remove",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
service_name: String|
|
||||||
|
-> Result<(), Box<EvalAltResult>> {
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.remove(&service_name)
|
||||||
|
.map_err(|e| format!("Failed to remove service: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"exists",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
service_name: String|
|
||||||
|
-> Result<bool, Box<EvalAltResult>> {
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.exists(&service_name)
|
||||||
|
.map_err(|e| format!("Failed to check if service exists: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"start_and_confirm",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
config: Map,
|
||||||
|
timeout_secs: i64|
|
||||||
|
-> Result<(), Box<EvalAltResult>> {
|
||||||
|
let service_config = map_to_service_config(config)?;
|
||||||
|
let timeout = if timeout_secs > 0 {
|
||||||
|
timeout_secs as u64
|
||||||
|
} else {
|
||||||
|
30
|
||||||
|
};
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.start_and_confirm(&service_config, timeout)
|
||||||
|
.map_err(|e| format!("Failed to start and confirm service: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
engine.register_fn(
|
||||||
|
"start_existing_and_confirm",
|
||||||
|
|manager: &mut RhaiServiceManager,
|
||||||
|
service_name: String,
|
||||||
|
timeout_secs: i64|
|
||||||
|
-> Result<(), Box<EvalAltResult>> {
|
||||||
|
let timeout = if timeout_secs > 0 {
|
||||||
|
timeout_secs as u64
|
||||||
|
} else {
|
||||||
|
30
|
||||||
|
};
|
||||||
|
manager
|
||||||
|
.inner
|
||||||
|
.start_existing_and_confirm(&service_name, timeout)
|
||||||
|
.map_err(|e| format!("Failed to start existing service and confirm: {}", e).into())
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert a Rhai Map to a ServiceConfig
|
||||||
|
fn map_to_service_config(map: Map) -> Result<ServiceConfig, Box<EvalAltResult>> {
|
||||||
|
let name = map
|
||||||
|
.get("name")
|
||||||
|
.and_then(|v| v.clone().into_string().ok())
|
||||||
|
.ok_or("Service config must have a 'name' field")?;
|
||||||
|
|
||||||
|
let binary_path = map
|
||||||
|
.get("binary_path")
|
||||||
|
.and_then(|v| v.clone().into_string().ok())
|
||||||
|
.ok_or("Service config must have a 'binary_path' field")?;
|
||||||
|
|
||||||
|
let args = map
|
||||||
|
.get("args")
|
||||||
|
.and_then(|v| v.clone().try_cast::<rhai::Array>())
|
||||||
|
.map(|arr| {
|
||||||
|
arr.into_iter()
|
||||||
|
.filter_map(|v| v.into_string().ok())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let working_directory = map
|
||||||
|
.get("working_directory")
|
||||||
|
.and_then(|v| v.clone().into_string().ok());
|
||||||
|
|
||||||
|
let environment = map
|
||||||
|
.get("environment")
|
||||||
|
.and_then(|v| v.clone().try_cast::<Map>())
|
||||||
|
.map(|env_map| {
|
||||||
|
env_map
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|(k, v)| v.into_string().ok().map(|val| (k.to_string(), val)))
|
||||||
|
.collect::<HashMap<String, String>>()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let auto_restart = map
|
||||||
|
.get("auto_restart")
|
||||||
|
.and_then(|v| v.as_bool().ok())
|
||||||
|
.unwrap_or(false);
|
||||||
|
|
||||||
|
Ok(ServiceConfig {
|
||||||
|
name,
|
||||||
|
binary_path,
|
||||||
|
args,
|
||||||
|
working_directory,
|
||||||
|
environment,
|
||||||
|
auto_restart,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use rhai::{Engine, Map};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_register_service_manager_module() {
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
register_service_manager_module(&mut engine).unwrap();
|
||||||
|
|
||||||
|
// Test that the functions are registered
|
||||||
|
// Note: Rhai doesn't expose a public API to check if functions are registered
|
||||||
|
// So we'll just verify the module registration doesn't panic
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_map_to_service_config() {
|
||||||
|
let mut map = Map::new();
|
||||||
|
map.insert("name".into(), "test-service".into());
|
||||||
|
map.insert("binary_path".into(), "/bin/echo".into());
|
||||||
|
map.insert("auto_restart".into(), true.into());
|
||||||
|
|
||||||
|
let config = map_to_service_config(map).unwrap();
|
||||||
|
assert_eq!(config.name, "test-service");
|
||||||
|
assert_eq!(config.binary_path, "/bin/echo");
|
||||||
|
assert_eq!(config.auto_restart, true);
|
||||||
|
}
|
||||||
|
}
|
434
_archive/service_manager/src/systemd.rs
Normal file
434
_archive/service_manager/src/systemd.rs
Normal file
@@ -0,0 +1,434 @@
|
|||||||
|
use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SystemdServiceManager {
|
||||||
|
service_prefix: String,
|
||||||
|
user_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SystemdServiceManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
service_prefix: "sal".to_string(),
|
||||||
|
user_mode: true, // Default to user services for safety
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_system() -> Self {
|
||||||
|
Self {
|
||||||
|
service_prefix: "sal".to_string(),
|
||||||
|
user_mode: false, // System-wide services (requires root)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_service_name(&self, service_name: &str) -> String {
|
||||||
|
format!("{}-{}.service", self.service_prefix, service_name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_unit_file_path(&self, service_name: &str) -> PathBuf {
|
||||||
|
let service_file = self.get_service_name(service_name);
|
||||||
|
if self.user_mode {
|
||||||
|
// User service directory
|
||||||
|
let home = std::env::var("HOME").unwrap_or_else(|_| "/tmp".to_string());
|
||||||
|
PathBuf::from(home)
|
||||||
|
.join(".config")
|
||||||
|
.join("systemd")
|
||||||
|
.join("user")
|
||||||
|
.join(service_file)
|
||||||
|
} else {
|
||||||
|
// System service directory
|
||||||
|
PathBuf::from("/etc/systemd/system").join(service_file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_systemctl(&self, args: &[&str]) -> Result<String, ServiceManagerError> {
|
||||||
|
let mut cmd = Command::new("systemctl");
|
||||||
|
|
||||||
|
if self.user_mode {
|
||||||
|
cmd.arg("--user");
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.args(args);
|
||||||
|
|
||||||
|
let output = cmd
|
||||||
|
.output()
|
||||||
|
.map_err(|e| ServiceManagerError::Other(format!("Failed to run systemctl: {}", e)))?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(ServiceManagerError::Other(format!(
|
||||||
|
"systemctl command failed: {}",
|
||||||
|
stderr
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_unit_file(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||||
|
let unit_path = self.get_unit_file_path(&config.name);
|
||||||
|
|
||||||
|
// Ensure the directory exists
|
||||||
|
if let Some(parent) = unit_path.parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to create unit directory: {}", e))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the unit file content
|
||||||
|
let mut unit_content = String::new();
|
||||||
|
unit_content.push_str("[Unit]\n");
|
||||||
|
unit_content.push_str(&format!("Description={} service\n", config.name));
|
||||||
|
unit_content.push_str("After=network.target\n\n");
|
||||||
|
|
||||||
|
unit_content.push_str("[Service]\n");
|
||||||
|
unit_content.push_str("Type=simple\n");
|
||||||
|
|
||||||
|
// Build the ExecStart command
|
||||||
|
let mut exec_start = config.binary_path.clone();
|
||||||
|
for arg in &config.args {
|
||||||
|
exec_start.push(' ');
|
||||||
|
exec_start.push_str(arg);
|
||||||
|
}
|
||||||
|
unit_content.push_str(&format!("ExecStart={}\n", exec_start));
|
||||||
|
|
||||||
|
if let Some(working_dir) = &config.working_directory {
|
||||||
|
unit_content.push_str(&format!("WorkingDirectory={}\n", working_dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add environment variables
|
||||||
|
for (key, value) in &config.environment {
|
||||||
|
unit_content.push_str(&format!("Environment=\"{}={}\"\n", key, value));
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.auto_restart {
|
||||||
|
unit_content.push_str("Restart=always\n");
|
||||||
|
unit_content.push_str("RestartSec=5\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
unit_content.push_str("\n[Install]\n");
|
||||||
|
unit_content.push_str("WantedBy=default.target\n");
|
||||||
|
|
||||||
|
// Write the unit file
|
||||||
|
fs::write(&unit_path, unit_content)
|
||||||
|
.map_err(|e| ServiceManagerError::Other(format!("Failed to write unit file: {}", e)))?;
|
||||||
|
|
||||||
|
// Reload systemd to pick up the new unit file
|
||||||
|
self.run_systemctl(&["daemon-reload"])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceManager for SystemdServiceManager {
|
||||||
|
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> {
|
||||||
|
let unit_path = self.get_unit_file_path(service_name);
|
||||||
|
Ok(unit_path.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||||
|
let service_name = self.get_service_name(&config.name);
|
||||||
|
|
||||||
|
// Check if service already exists and is running
|
||||||
|
if self.exists(&config.name)? {
|
||||||
|
match self.status(&config.name)? {
|
||||||
|
ServiceStatus::Running => {
|
||||||
|
return Err(ServiceManagerError::ServiceAlreadyExists(
|
||||||
|
config.name.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Service exists but not running, we can start it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create the unit file
|
||||||
|
self.create_unit_file(config)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enable and start the service
|
||||||
|
self.run_systemctl(&["enable", &service_name])
|
||||||
|
.map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?;
|
||||||
|
|
||||||
|
self.run_systemctl(&["start", &service_name])
|
||||||
|
.map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let service_unit = self.get_service_name(service_name);
|
||||||
|
|
||||||
|
// Check if unit file exists
|
||||||
|
if !self.exists(service_name)? {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if already running
|
||||||
|
match self.status(service_name)? {
|
||||||
|
ServiceStatus::Running => {
|
||||||
|
return Ok(()); // Already running, nothing to do
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Start the service
|
||||||
|
self.run_systemctl(&["start", &service_unit]).map_err(|e| {
|
||||||
|
ServiceManagerError::StartFailed(service_name.to_string(), e.to_string())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_and_confirm(
|
||||||
|
&self,
|
||||||
|
config: &ServiceConfig,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError> {
|
||||||
|
// Start the service first
|
||||||
|
self.start(config)?;
|
||||||
|
|
||||||
|
// Wait for confirmation with timeout
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
let timeout_duration = std::time::Duration::from_secs(timeout_secs);
|
||||||
|
|
||||||
|
while start_time.elapsed() < timeout_duration {
|
||||||
|
match self.status(&config.name) {
|
||||||
|
Ok(ServiceStatus::Running) => return Ok(()),
|
||||||
|
Ok(ServiceStatus::Failed) => {
|
||||||
|
return Err(ServiceManagerError::StartFailed(
|
||||||
|
config.name.clone(),
|
||||||
|
"Service failed to start".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
// Still starting, wait a bit
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
// Service might not exist yet, wait a bit
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ServiceManagerError::StartFailed(
|
||||||
|
config.name.clone(),
|
||||||
|
format!("Service did not start within {} seconds", timeout_secs),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_existing_and_confirm(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError> {
|
||||||
|
// Start the existing service first
|
||||||
|
self.start_existing(service_name)?;
|
||||||
|
|
||||||
|
// Wait for confirmation with timeout
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
let timeout_duration = std::time::Duration::from_secs(timeout_secs);
|
||||||
|
|
||||||
|
while start_time.elapsed() < timeout_duration {
|
||||||
|
match self.status(service_name) {
|
||||||
|
Ok(ServiceStatus::Running) => return Ok(()),
|
||||||
|
Ok(ServiceStatus::Failed) => {
|
||||||
|
return Err(ServiceManagerError::StartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
"Service failed to start".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
// Still starting, wait a bit
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
// Service might not exist yet, wait a bit
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ServiceManagerError::StartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
format!("Service did not start within {} seconds", timeout_secs),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let service_unit = self.get_service_name(service_name);
|
||||||
|
|
||||||
|
// Check if service exists
|
||||||
|
if !self.exists(service_name)? {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop the service
|
||||||
|
self.run_systemctl(&["stop", &service_unit]).map_err(|e| {
|
||||||
|
ServiceManagerError::StopFailed(service_name.to_string(), e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let service_unit = self.get_service_name(service_name);
|
||||||
|
|
||||||
|
// Check if service exists
|
||||||
|
if !self.exists(service_name)? {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the service
|
||||||
|
self.run_systemctl(&["restart", &service_unit])
|
||||||
|
.map_err(|e| {
|
||||||
|
ServiceManagerError::RestartFailed(service_name.to_string(), e.to_string())
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> {
|
||||||
|
let service_unit = self.get_service_name(service_name);
|
||||||
|
|
||||||
|
// Check if service exists
|
||||||
|
if !self.exists(service_name)? {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get service status
|
||||||
|
let output = self
|
||||||
|
.run_systemctl(&["is-active", &service_unit])
|
||||||
|
.unwrap_or_else(|_| "unknown".to_string());
|
||||||
|
|
||||||
|
let status = match output.trim() {
|
||||||
|
"active" => ServiceStatus::Running,
|
||||||
|
"inactive" => ServiceStatus::Stopped,
|
||||||
|
"failed" => ServiceStatus::Failed,
|
||||||
|
_ => ServiceStatus::Unknown,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logs(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
lines: Option<usize>,
|
||||||
|
) -> Result<String, ServiceManagerError> {
|
||||||
|
let service_unit = self.get_service_name(service_name);
|
||||||
|
|
||||||
|
// Check if service exists
|
||||||
|
if !self.exists(service_name)? {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build journalctl command
|
||||||
|
let mut args = vec!["--unit", &service_unit, "--no-pager"];
|
||||||
|
let lines_arg;
|
||||||
|
if let Some(n) = lines {
|
||||||
|
lines_arg = format!("--lines={}", n);
|
||||||
|
args.push(&lines_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use journalctl to get logs
|
||||||
|
let mut cmd = std::process::Command::new("journalctl");
|
||||||
|
if self.user_mode {
|
||||||
|
cmd.arg("--user");
|
||||||
|
}
|
||||||
|
cmd.args(&args);
|
||||||
|
|
||||||
|
let output = cmd.output().map_err(|e| {
|
||||||
|
ServiceManagerError::LogsFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
format!("Failed to run journalctl: {}", e),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
return Err(ServiceManagerError::LogsFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
format!("journalctl command failed: {}", stderr),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(&self) -> Result<Vec<String>, ServiceManagerError> {
|
||||||
|
// List all services with our prefix
|
||||||
|
let output =
|
||||||
|
self.run_systemctl(&["list-units", "--type=service", "--all", "--no-pager"])?;
|
||||||
|
|
||||||
|
let mut services = Vec::new();
|
||||||
|
for line in output.lines() {
|
||||||
|
if line.contains(&format!("{}-", self.service_prefix)) {
|
||||||
|
// Extract service name from the line
|
||||||
|
if let Some(unit_name) = line.split_whitespace().next() {
|
||||||
|
if let Some(service_name) = unit_name.strip_suffix(".service") {
|
||||||
|
if let Some(name) =
|
||||||
|
service_name.strip_prefix(&format!("{}-", self.service_prefix))
|
||||||
|
{
|
||||||
|
services.push(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(services)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let service_unit = self.get_service_name(service_name);
|
||||||
|
|
||||||
|
// Check if service exists
|
||||||
|
if !self.exists(service_name)? {
|
||||||
|
return Err(ServiceManagerError::ServiceNotFound(
|
||||||
|
service_name.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to stop the service first, but don't fail if it's already stopped
|
||||||
|
if let Err(e) = self.stop(service_name) {
|
||||||
|
log::warn!(
|
||||||
|
"Failed to stop service '{}' before removal: {}",
|
||||||
|
service_name,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable the service
|
||||||
|
if let Err(e) = self.run_systemctl(&["disable", &service_unit]) {
|
||||||
|
log::warn!("Failed to disable service '{}': {}", service_name, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the unit file
|
||||||
|
let unit_path = self.get_unit_file_path(service_name);
|
||||||
|
if unit_path.exists() {
|
||||||
|
std::fs::remove_file(&unit_path).map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to remove unit file: {}", e))
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload systemd to pick up the changes
|
||||||
|
self.run_systemctl(&["daemon-reload"])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
379
_archive/service_manager/src/zinit.rs
Normal file
379
_archive/service_manager/src/zinit.rs
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
use crate::{ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus};
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use serde_json::json;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::runtime::Runtime;
|
||||||
|
use tokio::time::timeout;
|
||||||
|
use zinit_client::{ServiceStatus as ZinitServiceStatus, ZinitClient, ZinitError};
|
||||||
|
|
||||||
|
// Shared runtime for async operations - production-safe initialization
|
||||||
|
static ASYNC_RUNTIME: Lazy<Option<Runtime>> = Lazy::new(|| Runtime::new().ok());
|
||||||
|
|
||||||
|
/// Get the async runtime, creating a temporary one if the static runtime failed
|
||||||
|
fn get_runtime() -> Result<Runtime, ServiceManagerError> {
|
||||||
|
// Try to use the static runtime first
|
||||||
|
if let Some(_runtime) = ASYNC_RUNTIME.as_ref() {
|
||||||
|
// We can't return a reference to the static runtime because we need ownership
|
||||||
|
// for block_on, so we create a new one. This is a reasonable trade-off for safety.
|
||||||
|
Runtime::new().map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Static runtime failed, try to create a new one
|
||||||
|
Runtime::new().map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to create async runtime: {}", e))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ZinitServiceManager {
|
||||||
|
client: Arc<ZinitClient>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ZinitServiceManager {
|
||||||
|
pub fn new(socket_path: &str) -> Result<Self, ServiceManagerError> {
|
||||||
|
// Create the base zinit client directly
|
||||||
|
let client = Arc::new(ZinitClient::new(socket_path));
|
||||||
|
|
||||||
|
Ok(ZinitServiceManager { client })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute an async operation using the shared runtime or current context
|
||||||
|
fn execute_async<F, T>(&self, operation: F) -> Result<T, ServiceManagerError>
|
||||||
|
where
|
||||||
|
F: std::future::Future<Output = Result<T, ZinitError>> + Send + 'static,
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
// Check if we're already in a tokio runtime context
|
||||||
|
if let Ok(_handle) = tokio::runtime::Handle::try_current() {
|
||||||
|
// We're in an async context, use spawn_blocking to avoid nested runtime
|
||||||
|
let result = std::thread::spawn(
|
||||||
|
move || -> Result<Result<T, ZinitError>, ServiceManagerError> {
|
||||||
|
let rt = Runtime::new().map_err(|e| {
|
||||||
|
ServiceManagerError::Other(format!("Failed to create runtime: {}", e))
|
||||||
|
})?;
|
||||||
|
Ok(rt.block_on(operation))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.join()
|
||||||
|
.map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?;
|
||||||
|
result?.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||||
|
} else {
|
||||||
|
// No current runtime, use production-safe runtime
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime
|
||||||
|
.block_on(operation)
|
||||||
|
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute an async operation with timeout using the shared runtime or current context
|
||||||
|
fn execute_async_with_timeout<F, T>(
|
||||||
|
&self,
|
||||||
|
operation: F,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<T, ServiceManagerError>
|
||||||
|
where
|
||||||
|
F: std::future::Future<Output = Result<T, ZinitError>> + Send + 'static,
|
||||||
|
T: Send + 'static,
|
||||||
|
{
|
||||||
|
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||||
|
let timeout_op = timeout(timeout_duration, operation);
|
||||||
|
|
||||||
|
// Check if we're already in a tokio runtime context
|
||||||
|
if let Ok(_handle) = tokio::runtime::Handle::try_current() {
|
||||||
|
// We're in an async context, use spawn_blocking to avoid nested runtime
|
||||||
|
let result = std::thread::spawn(move || {
|
||||||
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
|
rt.block_on(timeout_op)
|
||||||
|
})
|
||||||
|
.join()
|
||||||
|
.map_err(|_| ServiceManagerError::Other("Thread join failed".to_string()))?;
|
||||||
|
|
||||||
|
result
|
||||||
|
.map_err(|_| {
|
||||||
|
ServiceManagerError::Other(format!(
|
||||||
|
"Operation timed out after {} seconds",
|
||||||
|
timeout_secs
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||||
|
} else {
|
||||||
|
// No current runtime, use production-safe runtime
|
||||||
|
let runtime = get_runtime()?;
|
||||||
|
runtime
|
||||||
|
.block_on(timeout_op)
|
||||||
|
.map_err(|_| {
|
||||||
|
ServiceManagerError::Other(format!(
|
||||||
|
"Operation timed out after {} seconds",
|
||||||
|
timeout_secs
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceManager for ZinitServiceManager {
|
||||||
|
fn exists(&self, service_name: &str) -> Result<bool, ServiceManagerError> {
|
||||||
|
let status_res = self.status(service_name);
|
||||||
|
match status_res {
|
||||||
|
Ok(_) => Ok(true),
|
||||||
|
Err(ServiceManagerError::ServiceNotFound(_)) => Ok(false),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start(&self, config: &ServiceConfig) -> Result<(), ServiceManagerError> {
|
||||||
|
// Build the exec command with args
|
||||||
|
let mut exec_command = config.binary_path.clone();
|
||||||
|
if !config.args.is_empty() {
|
||||||
|
exec_command.push(' ');
|
||||||
|
exec_command.push_str(&config.args.join(" "));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create zinit-compatible service configuration
|
||||||
|
let mut service_config = json!({
|
||||||
|
"exec": exec_command,
|
||||||
|
"oneshot": !config.auto_restart, // zinit uses oneshot, not restart
|
||||||
|
"env": config.environment,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add optional fields if present
|
||||||
|
if let Some(ref working_dir) = config.working_directory {
|
||||||
|
// Zinit doesn't support working_directory directly, so we need to modify the exec command
|
||||||
|
let cd_command = format!("cd {} && {}", working_dir, exec_command);
|
||||||
|
service_config["exec"] = json!(cd_command);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let service_name = config.name.clone();
|
||||||
|
self.execute_async(
|
||||||
|
async move { client.create_service(&service_name, service_config).await },
|
||||||
|
)
|
||||||
|
.map_err(|e| ServiceManagerError::StartFailed(config.name.clone(), e.to_string()))?;
|
||||||
|
|
||||||
|
self.start_existing(&config.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_existing(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let service_name_owned = service_name.to_string();
|
||||||
|
let service_name_for_error = service_name.to_string();
|
||||||
|
self.execute_async(async move { client.start(&service_name_owned).await })
|
||||||
|
.map_err(|e| ServiceManagerError::StartFailed(service_name_for_error, e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_and_confirm(
|
||||||
|
&self,
|
||||||
|
config: &ServiceConfig,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError> {
|
||||||
|
// Start the service first
|
||||||
|
self.start(config)?;
|
||||||
|
|
||||||
|
// Wait for confirmation with timeout using the shared runtime
|
||||||
|
self.execute_async_with_timeout(
|
||||||
|
async move {
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||||
|
|
||||||
|
while start_time.elapsed() < timeout_duration {
|
||||||
|
// We need to call status in a blocking way from within the async context
|
||||||
|
// For now, we'll use a simple polling approach
|
||||||
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a timeout error that will be handled by execute_async_with_timeout
|
||||||
|
// Use a generic error since we don't know the exact ZinitError variants
|
||||||
|
Err(ZinitError::from(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::TimedOut,
|
||||||
|
"Timeout waiting for service confirmation",
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
timeout_secs,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Check final status
|
||||||
|
match self.status(&config.name)? {
|
||||||
|
ServiceStatus::Running => Ok(()),
|
||||||
|
ServiceStatus::Failed => Err(ServiceManagerError::StartFailed(
|
||||||
|
config.name.clone(),
|
||||||
|
"Service failed to start".to_string(),
|
||||||
|
)),
|
||||||
|
_ => Err(ServiceManagerError::StartFailed(
|
||||||
|
config.name.clone(),
|
||||||
|
format!("Service did not start within {} seconds", timeout_secs),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn start_existing_and_confirm(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
timeout_secs: u64,
|
||||||
|
) -> Result<(), ServiceManagerError> {
|
||||||
|
// Start the existing service first
|
||||||
|
self.start_existing(service_name)?;
|
||||||
|
|
||||||
|
// Wait for confirmation with timeout using the shared runtime
|
||||||
|
self.execute_async_with_timeout(
|
||||||
|
async move {
|
||||||
|
let start_time = std::time::Instant::now();
|
||||||
|
let timeout_duration = Duration::from_secs(timeout_secs);
|
||||||
|
|
||||||
|
while start_time.elapsed() < timeout_duration {
|
||||||
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return a timeout error that will be handled by execute_async_with_timeout
|
||||||
|
// Use a generic error since we don't know the exact ZinitError variants
|
||||||
|
Err(ZinitError::from(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::TimedOut,
|
||||||
|
"Timeout waiting for service confirmation",
|
||||||
|
)))
|
||||||
|
},
|
||||||
|
timeout_secs,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Check final status
|
||||||
|
match self.status(service_name)? {
|
||||||
|
ServiceStatus::Running => Ok(()),
|
||||||
|
ServiceStatus::Failed => Err(ServiceManagerError::StartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
"Service failed to start".to_string(),
|
||||||
|
)),
|
||||||
|
_ => Err(ServiceManagerError::StartFailed(
|
||||||
|
service_name.to_string(),
|
||||||
|
format!("Service did not start within {} seconds", timeout_secs),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let service_name_owned = service_name.to_string();
|
||||||
|
let service_name_for_error = service_name.to_string();
|
||||||
|
self.execute_async(async move { client.stop(&service_name_owned).await })
|
||||||
|
.map_err(|e| ServiceManagerError::StopFailed(service_name_for_error, e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restart(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let service_name_owned = service_name.to_string();
|
||||||
|
let service_name_for_error = service_name.to_string();
|
||||||
|
self.execute_async(async move { client.restart(&service_name_owned).await })
|
||||||
|
.map_err(|e| ServiceManagerError::RestartFailed(service_name_for_error, e.to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn status(&self, service_name: &str) -> Result<ServiceStatus, ServiceManagerError> {
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let service_name_owned = service_name.to_string();
|
||||||
|
let service_name_for_error = service_name.to_string();
|
||||||
|
let status: ZinitServiceStatus = self
|
||||||
|
.execute_async(async move { client.status(&service_name_owned).await })
|
||||||
|
.map_err(|e| {
|
||||||
|
// Check if this is a "service not found" error
|
||||||
|
if e.to_string().contains("not found") || e.to_string().contains("does not exist") {
|
||||||
|
ServiceManagerError::ServiceNotFound(service_name_for_error)
|
||||||
|
} else {
|
||||||
|
ServiceManagerError::Other(e.to_string())
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
|
||||||
|
// ServiceStatus is a struct with fields, not an enum
|
||||||
|
// We need to check the state field to determine the status
|
||||||
|
// Convert ServiceState to string and match on that
|
||||||
|
let state_str = format!("{:?}", status.state).to_lowercase();
|
||||||
|
let service_status = match state_str.as_str() {
|
||||||
|
s if s.contains("running") => crate::ServiceStatus::Running,
|
||||||
|
s if s.contains("stopped") => crate::ServiceStatus::Stopped,
|
||||||
|
s if s.contains("failed") => crate::ServiceStatus::Failed,
|
||||||
|
_ => crate::ServiceStatus::Unknown,
|
||||||
|
};
|
||||||
|
Ok(service_status)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn logs(
|
||||||
|
&self,
|
||||||
|
service_name: &str,
|
||||||
|
_lines: Option<usize>,
|
||||||
|
) -> Result<String, ServiceManagerError> {
|
||||||
|
// The logs method takes (follow: bool, filter: Option<impl AsRef<str>>)
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let service_name_owned = service_name.to_string();
|
||||||
|
let logs = self
|
||||||
|
.execute_async(async move {
|
||||||
|
use futures::StreamExt;
|
||||||
|
use tokio::time::{timeout, Duration};
|
||||||
|
|
||||||
|
let mut log_stream = client
|
||||||
|
.logs(false, Some(service_name_owned.as_str()))
|
||||||
|
.await?;
|
||||||
|
let mut logs = Vec::new();
|
||||||
|
|
||||||
|
// Collect logs from the stream with a reasonable limit
|
||||||
|
let mut count = 0;
|
||||||
|
const MAX_LOGS: usize = 100;
|
||||||
|
const LOG_TIMEOUT: Duration = Duration::from_secs(5);
|
||||||
|
|
||||||
|
// Use timeout to prevent hanging
|
||||||
|
let result = timeout(LOG_TIMEOUT, async {
|
||||||
|
while let Some(log_result) = log_stream.next().await {
|
||||||
|
match log_result {
|
||||||
|
Ok(log_entry) => {
|
||||||
|
logs.push(format!("{:?}", log_entry));
|
||||||
|
count += 1;
|
||||||
|
if count >= MAX_LOGS {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// Handle timeout - this is not an error, just means no more logs available
|
||||||
|
if result.is_err() {
|
||||||
|
log::debug!(
|
||||||
|
"Log reading timed out after {} seconds, returning {} logs",
|
||||||
|
LOG_TIMEOUT.as_secs(),
|
||||||
|
logs.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok::<Vec<String>, ZinitError>(logs)
|
||||||
|
})
|
||||||
|
.map_err(|e| {
|
||||||
|
ServiceManagerError::LogsFailed(service_name.to_string(), e.to_string())
|
||||||
|
})?;
|
||||||
|
Ok(logs.join("\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn list(&self) -> Result<Vec<String>, ServiceManagerError> {
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let services = self
|
||||||
|
.execute_async(async move { client.list().await })
|
||||||
|
.map_err(|e| ServiceManagerError::Other(e.to_string()))?;
|
||||||
|
Ok(services.keys().cloned().collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove(&self, service_name: &str) -> Result<(), ServiceManagerError> {
|
||||||
|
// Try to stop the service first, but don't fail if it's already stopped or doesn't exist
|
||||||
|
if let Err(e) = self.stop(service_name) {
|
||||||
|
// Log the error but continue with removal
|
||||||
|
log::warn!(
|
||||||
|
"Failed to stop service '{}' before removal: {}",
|
||||||
|
service_name,
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let client = Arc::clone(&self.client);
|
||||||
|
let service_name = service_name.to_string();
|
||||||
|
self.execute_async(async move { client.delete_service(&service_name).await })
|
||||||
|
.map_err(|e| ServiceManagerError::Other(e.to_string()))
|
||||||
|
}
|
||||||
|
}
|
243
_archive/service_manager/tests/factory_tests.rs
Normal file
243
_archive/service_manager/tests/factory_tests.rs
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
use sal_service_manager::{create_service_manager, ServiceConfig, ServiceManager};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_service_manager() {
|
||||||
|
// Test that the factory function creates the appropriate service manager for the platform
|
||||||
|
let manager = create_service_manager().expect("Failed to create service manager");
|
||||||
|
|
||||||
|
// Test basic functionality - should be able to call methods without panicking
|
||||||
|
let list_result = manager.list();
|
||||||
|
|
||||||
|
// The result might be an error (if no service system is available), but it shouldn't panic
|
||||||
|
match list_result {
|
||||||
|
Ok(services) => {
|
||||||
|
println!(
|
||||||
|
"✓ Service manager created successfully, found {} services",
|
||||||
|
services.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✓ Service manager created, but got expected error: {}", e);
|
||||||
|
// This is expected on systems without the appropriate service manager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_config_creation() {
|
||||||
|
// Test creating various service configurations
|
||||||
|
let basic_config = ServiceConfig {
|
||||||
|
name: "test-service".to_string(),
|
||||||
|
binary_path: "/usr/bin/echo".to_string(),
|
||||||
|
args: vec!["hello".to_string(), "world".to_string()],
|
||||||
|
working_directory: None,
|
||||||
|
environment: HashMap::new(),
|
||||||
|
auto_restart: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(basic_config.name, "test-service");
|
||||||
|
assert_eq!(basic_config.binary_path, "/usr/bin/echo");
|
||||||
|
assert_eq!(basic_config.args.len(), 2);
|
||||||
|
assert_eq!(basic_config.args[0], "hello");
|
||||||
|
assert_eq!(basic_config.args[1], "world");
|
||||||
|
assert!(basic_config.working_directory.is_none());
|
||||||
|
assert!(basic_config.environment.is_empty());
|
||||||
|
assert!(!basic_config.auto_restart);
|
||||||
|
|
||||||
|
println!("✓ Basic service config created successfully");
|
||||||
|
|
||||||
|
// Test config with environment variables
|
||||||
|
let mut env = HashMap::new();
|
||||||
|
env.insert("PATH".to_string(), "/usr/bin:/bin".to_string());
|
||||||
|
env.insert("HOME".to_string(), "/tmp".to_string());
|
||||||
|
|
||||||
|
let env_config = ServiceConfig {
|
||||||
|
name: "env-service".to_string(),
|
||||||
|
binary_path: "/usr/bin/env".to_string(),
|
||||||
|
args: vec![],
|
||||||
|
working_directory: Some("/tmp".to_string()),
|
||||||
|
environment: env.clone(),
|
||||||
|
auto_restart: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(env_config.name, "env-service");
|
||||||
|
assert_eq!(env_config.binary_path, "/usr/bin/env");
|
||||||
|
assert!(env_config.args.is_empty());
|
||||||
|
assert_eq!(env_config.working_directory, Some("/tmp".to_string()));
|
||||||
|
assert_eq!(env_config.environment.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
env_config.environment.get("PATH"),
|
||||||
|
Some(&"/usr/bin:/bin".to_string())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
env_config.environment.get("HOME"),
|
||||||
|
Some(&"/tmp".to_string())
|
||||||
|
);
|
||||||
|
assert!(env_config.auto_restart);
|
||||||
|
|
||||||
|
println!("✓ Environment service config created successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_config_clone() {
|
||||||
|
// Test that ServiceConfig can be cloned
|
||||||
|
let original_config = ServiceConfig {
|
||||||
|
name: "original".to_string(),
|
||||||
|
binary_path: "/bin/sh".to_string(),
|
||||||
|
args: vec!["-c".to_string(), "echo test".to_string()],
|
||||||
|
working_directory: Some("/home".to_string()),
|
||||||
|
environment: {
|
||||||
|
let mut env = HashMap::new();
|
||||||
|
env.insert("TEST".to_string(), "value".to_string());
|
||||||
|
env
|
||||||
|
},
|
||||||
|
auto_restart: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
let cloned_config = original_config.clone();
|
||||||
|
|
||||||
|
assert_eq!(original_config.name, cloned_config.name);
|
||||||
|
assert_eq!(original_config.binary_path, cloned_config.binary_path);
|
||||||
|
assert_eq!(original_config.args, cloned_config.args);
|
||||||
|
assert_eq!(
|
||||||
|
original_config.working_directory,
|
||||||
|
cloned_config.working_directory
|
||||||
|
);
|
||||||
|
assert_eq!(original_config.environment, cloned_config.environment);
|
||||||
|
assert_eq!(original_config.auto_restart, cloned_config.auto_restart);
|
||||||
|
|
||||||
|
println!("✓ Service config cloning works correctly");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
#[test]
|
||||||
|
fn test_macos_service_manager() {
|
||||||
|
use sal_service_manager::LaunchctlServiceManager;
|
||||||
|
|
||||||
|
// Test creating macOS-specific service manager
|
||||||
|
let manager = LaunchctlServiceManager::new();
|
||||||
|
|
||||||
|
// Test basic functionality
|
||||||
|
let list_result = manager.list();
|
||||||
|
match list_result {
|
||||||
|
Ok(services) => {
|
||||||
|
println!(
|
||||||
|
"✓ macOS LaunchctlServiceManager created successfully, found {} services",
|
||||||
|
services.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!(
|
||||||
|
"✓ macOS LaunchctlServiceManager created, but got expected error: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
#[test]
|
||||||
|
fn test_linux_service_manager() {
|
||||||
|
use sal_service_manager::SystemdServiceManager;
|
||||||
|
|
||||||
|
// Test creating Linux-specific service manager
|
||||||
|
let manager = SystemdServiceManager::new();
|
||||||
|
|
||||||
|
// Test basic functionality
|
||||||
|
let list_result = manager.list();
|
||||||
|
match list_result {
|
||||||
|
Ok(services) => {
|
||||||
|
println!(
|
||||||
|
"✓ Linux SystemdServiceManager created successfully, found {} services",
|
||||||
|
services.len()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!(
|
||||||
|
"✓ Linux SystemdServiceManager created, but got expected error: {}",
|
||||||
|
e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_status_debug() {
|
||||||
|
use sal_service_manager::ServiceStatus;
|
||||||
|
|
||||||
|
// Test that ServiceStatus can be debugged and cloned
|
||||||
|
let statuses = vec![
|
||||||
|
ServiceStatus::Running,
|
||||||
|
ServiceStatus::Stopped,
|
||||||
|
ServiceStatus::Failed,
|
||||||
|
ServiceStatus::Unknown,
|
||||||
|
];
|
||||||
|
|
||||||
|
for status in &statuses {
|
||||||
|
let cloned = status.clone();
|
||||||
|
let debug_str = format!("{:?}", status);
|
||||||
|
|
||||||
|
assert!(!debug_str.is_empty());
|
||||||
|
assert_eq!(status, &cloned);
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"✓ ServiceStatus::{:?} debug and clone work correctly",
|
||||||
|
status
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_manager_error_debug() {
|
||||||
|
use sal_service_manager::ServiceManagerError;
|
||||||
|
|
||||||
|
// Test that ServiceManagerError can be debugged and displayed
|
||||||
|
let errors = vec![
|
||||||
|
ServiceManagerError::ServiceNotFound("test".to_string()),
|
||||||
|
ServiceManagerError::ServiceAlreadyExists("test".to_string()),
|
||||||
|
ServiceManagerError::StartFailed("test".to_string(), "reason".to_string()),
|
||||||
|
ServiceManagerError::StopFailed("test".to_string(), "reason".to_string()),
|
||||||
|
ServiceManagerError::RestartFailed("test".to_string(), "reason".to_string()),
|
||||||
|
ServiceManagerError::LogsFailed("test".to_string(), "reason".to_string()),
|
||||||
|
ServiceManagerError::Other("generic error".to_string()),
|
||||||
|
];
|
||||||
|
|
||||||
|
for error in &errors {
|
||||||
|
let debug_str = format!("{:?}", error);
|
||||||
|
let display_str = format!("{}", error);
|
||||||
|
|
||||||
|
assert!(!debug_str.is_empty());
|
||||||
|
assert!(!display_str.is_empty());
|
||||||
|
|
||||||
|
println!("✓ Error debug: {:?}", error);
|
||||||
|
println!("✓ Error display: {}", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_service_manager_trait_object() {
|
||||||
|
// Test that we can use ServiceManager as a trait object
|
||||||
|
let manager: Box<dyn ServiceManager> =
|
||||||
|
create_service_manager().expect("Failed to create service manager");
|
||||||
|
|
||||||
|
// Test that we can call methods through the trait object
|
||||||
|
let list_result = manager.list();
|
||||||
|
|
||||||
|
match list_result {
|
||||||
|
Ok(services) => {
|
||||||
|
println!("✓ Trait object works, found {} services", services.len());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✓ Trait object works, got expected error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test exists method
|
||||||
|
let exists_result = manager.exists("non-existent-service");
|
||||||
|
match exists_result {
|
||||||
|
Ok(false) => println!("✓ Trait object exists method works correctly"),
|
||||||
|
Ok(true) => println!("⚠ Unexpectedly found non-existent service"),
|
||||||
|
Err(_) => println!("✓ Trait object exists method works (with error)"),
|
||||||
|
}
|
||||||
|
}
|
177
_archive/service_manager/tests/rhai/service_lifecycle.rhai
Normal file
177
_archive/service_manager/tests/rhai/service_lifecycle.rhai
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
// Service lifecycle management test script
|
||||||
|
// This script tests REAL complete service lifecycle scenarios
|
||||||
|
|
||||||
|
print("=== Service Lifecycle Management Test ===");
|
||||||
|
|
||||||
|
// Create service manager
|
||||||
|
let manager = create_service_manager();
|
||||||
|
print("✓ Service manager created");
|
||||||
|
|
||||||
|
// Test configuration - real services for testing
|
||||||
|
let test_services = [
|
||||||
|
#{
|
||||||
|
name: "lifecycle-test-1",
|
||||||
|
binary_path: "/bin/echo",
|
||||||
|
args: ["Lifecycle test 1"],
|
||||||
|
working_directory: "/tmp",
|
||||||
|
environment: #{},
|
||||||
|
auto_restart: false
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
name: "lifecycle-test-2",
|
||||||
|
binary_path: "/bin/echo",
|
||||||
|
args: ["Lifecycle test 2"],
|
||||||
|
working_directory: "/tmp",
|
||||||
|
environment: #{ "TEST_VAR": "test_value" },
|
||||||
|
auto_restart: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let total_tests = 0;
|
||||||
|
let passed_tests = 0;
|
||||||
|
|
||||||
|
// Test 1: Service Creation and Start
|
||||||
|
print("\n1. Testing service creation and start...");
|
||||||
|
for service_config in test_services {
|
||||||
|
print(`\nStarting service: ${service_config.name}`);
|
||||||
|
try {
|
||||||
|
start(manager, service_config);
|
||||||
|
print(` ✓ Service ${service_config.name} started successfully`);
|
||||||
|
passed_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(` ✗ Service ${service_config.name} start failed: ${e}`);
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 2: Service Existence Check
|
||||||
|
print("\n2. Testing service existence checks...");
|
||||||
|
for service_config in test_services {
|
||||||
|
print(`\nChecking existence of: ${service_config.name}`);
|
||||||
|
try {
|
||||||
|
let service_exists = exists(manager, service_config.name);
|
||||||
|
if service_exists {
|
||||||
|
print(` ✓ Service ${service_config.name} exists: ${service_exists}`);
|
||||||
|
passed_tests += 1;
|
||||||
|
} else {
|
||||||
|
print(` ✗ Service ${service_config.name} doesn't exist after start`);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
print(` ✗ Existence check failed for ${service_config.name}: ${e}`);
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Status Check
|
||||||
|
print("\n3. Testing status checks...");
|
||||||
|
for service_config in test_services {
|
||||||
|
print(`\nChecking status of: ${service_config.name}`);
|
||||||
|
try {
|
||||||
|
let service_status = status(manager, service_config.name);
|
||||||
|
print(` ✓ Service ${service_config.name} status: ${service_status}`);
|
||||||
|
passed_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(` ✗ Status check failed for ${service_config.name}: ${e}`);
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Service List Check
|
||||||
|
print("\n4. Testing service list...");
|
||||||
|
try {
|
||||||
|
let services = list(manager);
|
||||||
|
print(` ✓ Service list retrieved (${services.len()} services)`);
|
||||||
|
|
||||||
|
// Check if our test services are in the list
|
||||||
|
for service_config in test_services {
|
||||||
|
let found = false;
|
||||||
|
for service in services {
|
||||||
|
if service.contains(service_config.name) {
|
||||||
|
found = true;
|
||||||
|
print(` ✓ Found ${service_config.name} in list`);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
print(` ⚠ ${service_config.name} not found in service list`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
passed_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(` ✗ Service list failed: ${e}`);
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
|
||||||
|
// Test 5: Service Stop
|
||||||
|
print("\n5. Testing service stop...");
|
||||||
|
for service_config in test_services {
|
||||||
|
print(`\nStopping service: ${service_config.name}`);
|
||||||
|
try {
|
||||||
|
stop(manager, service_config.name);
|
||||||
|
print(` ✓ Service ${service_config.name} stopped successfully`);
|
||||||
|
passed_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(` ✗ Service ${service_config.name} stop failed: ${e}`);
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: Service Removal
|
||||||
|
print("\n6. Testing service removal...");
|
||||||
|
for service_config in test_services {
|
||||||
|
print(`\nRemoving service: ${service_config.name}`);
|
||||||
|
try {
|
||||||
|
remove(manager, service_config.name);
|
||||||
|
print(` ✓ Service ${service_config.name} removed successfully`);
|
||||||
|
passed_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(` ✗ Service ${service_config.name} removal failed: ${e}`);
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Cleanup Verification
|
||||||
|
print("\n7. Testing cleanup verification...");
|
||||||
|
for service_config in test_services {
|
||||||
|
print(`\nVerifying removal of: ${service_config.name}`);
|
||||||
|
try {
|
||||||
|
let exists_after_remove = exists(manager, service_config.name);
|
||||||
|
if !exists_after_remove {
|
||||||
|
print(` ✓ Service ${service_config.name} correctly doesn't exist after removal`);
|
||||||
|
passed_tests += 1;
|
||||||
|
} else {
|
||||||
|
print(` ✗ Service ${service_config.name} still exists after removal`);
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
print(` ✗ Cleanup verification failed for ${service_config.name}: ${e}`);
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Summary
|
||||||
|
print("\n=== Lifecycle Test Summary ===");
|
||||||
|
print(`Services tested: ${test_services.len()}`);
|
||||||
|
print(`Total operations: ${total_tests}`);
|
||||||
|
print(`Successful operations: ${passed_tests}`);
|
||||||
|
print(`Failed operations: ${total_tests - passed_tests}`);
|
||||||
|
print(`Success rate: ${(passed_tests * 100) / total_tests}%`);
|
||||||
|
|
||||||
|
if passed_tests == total_tests {
|
||||||
|
print("\n🎉 All lifecycle tests passed!");
|
||||||
|
print("Service manager is working correctly across all scenarios.");
|
||||||
|
} else {
|
||||||
|
print(`\n⚠ ${total_tests - passed_tests} test(s) failed`);
|
||||||
|
print("Some service manager operations need attention.");
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n=== Service Lifecycle Test Complete ===");
|
||||||
|
|
||||||
|
// Return test results
|
||||||
|
#{
|
||||||
|
summary: #{
|
||||||
|
total_tests: total_tests,
|
||||||
|
passed_tests: passed_tests,
|
||||||
|
success_rate: (passed_tests * 100) / total_tests,
|
||||||
|
services_tested: test_services.len()
|
||||||
|
}
|
||||||
|
}
|
218
_archive/service_manager/tests/rhai/service_manager_basic.rhai
Normal file
218
_archive/service_manager/tests/rhai/service_manager_basic.rhai
Normal file
@@ -0,0 +1,218 @@
|
|||||||
|
// Basic service manager functionality test script
|
||||||
|
// This script tests the REAL service manager through Rhai integration
|
||||||
|
|
||||||
|
print("=== Service Manager Basic Functionality Test ===");
|
||||||
|
|
||||||
|
// Test configuration
|
||||||
|
let test_service_name = "rhai-test-service";
|
||||||
|
let test_binary = "/bin/echo";
|
||||||
|
let test_args = ["Hello from Rhai service manager test"];
|
||||||
|
|
||||||
|
print(`Testing service: ${test_service_name}`);
|
||||||
|
print(`Binary: ${test_binary}`);
|
||||||
|
print(`Args: ${test_args}`);
|
||||||
|
|
||||||
|
// Test results tracking
|
||||||
|
let test_results = #{
|
||||||
|
creation: "NOT_RUN",
|
||||||
|
exists_before: "NOT_RUN",
|
||||||
|
start: "NOT_RUN",
|
||||||
|
exists_after: "NOT_RUN",
|
||||||
|
status: "NOT_RUN",
|
||||||
|
list: "NOT_RUN",
|
||||||
|
stop: "NOT_RUN",
|
||||||
|
remove: "NOT_RUN",
|
||||||
|
cleanup: "NOT_RUN"
|
||||||
|
};
|
||||||
|
|
||||||
|
let passed_tests = 0;
|
||||||
|
let total_tests = 0;
|
||||||
|
|
||||||
|
// Test 1: Service Manager Creation
|
||||||
|
print("\n1. Testing service manager creation...");
|
||||||
|
try {
|
||||||
|
let manager = create_service_manager();
|
||||||
|
print("✓ Service manager created successfully");
|
||||||
|
test_results["creation"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service manager creation failed: ${e}`);
|
||||||
|
test_results["creation"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
// Return early if we can't create the manager
|
||||||
|
return test_results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the service manager for all subsequent tests
|
||||||
|
let manager = create_service_manager();
|
||||||
|
|
||||||
|
// Test 2: Check if service exists before creation
|
||||||
|
print("\n2. Testing service existence check (before creation)...");
|
||||||
|
try {
|
||||||
|
let exists_before = exists(manager, test_service_name);
|
||||||
|
print(`✓ Service existence check: ${exists_before}`);
|
||||||
|
|
||||||
|
if !exists_before {
|
||||||
|
print("✓ Service correctly doesn't exist before creation");
|
||||||
|
test_results["exists_before"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
} else {
|
||||||
|
print("⚠ Service unexpectedly exists before creation");
|
||||||
|
test_results["exists_before"] = "WARN";
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service existence check failed: ${e}`);
|
||||||
|
test_results["exists_before"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 3: Start the service
|
||||||
|
print("\n3. Testing service start...");
|
||||||
|
try {
|
||||||
|
// Create a service configuration object
|
||||||
|
let service_config = #{
|
||||||
|
name: test_service_name,
|
||||||
|
binary_path: test_binary,
|
||||||
|
args: test_args,
|
||||||
|
working_directory: "/tmp",
|
||||||
|
environment: #{},
|
||||||
|
auto_restart: false
|
||||||
|
};
|
||||||
|
|
||||||
|
start(manager, service_config);
|
||||||
|
print("✓ Service started successfully");
|
||||||
|
test_results["start"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service start failed: ${e}`);
|
||||||
|
test_results["start"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 4: Check if service exists after creation
|
||||||
|
print("\n4. Testing service existence check (after creation)...");
|
||||||
|
try {
|
||||||
|
let exists_after = exists(manager, test_service_name);
|
||||||
|
print(`✓ Service existence check: ${exists_after}`);
|
||||||
|
|
||||||
|
if exists_after {
|
||||||
|
print("✓ Service correctly exists after creation");
|
||||||
|
test_results["exists_after"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
} else {
|
||||||
|
print("✗ Service doesn't exist after creation");
|
||||||
|
test_results["exists_after"] = "FAIL";
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service existence check failed: ${e}`);
|
||||||
|
test_results["exists_after"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 5: Check service status
|
||||||
|
print("\n5. Testing service status...");
|
||||||
|
try {
|
||||||
|
let service_status = status(manager, test_service_name);
|
||||||
|
print(`✓ Service status: ${service_status}`);
|
||||||
|
test_results["status"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service status check failed: ${e}`);
|
||||||
|
test_results["status"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 6: List services
|
||||||
|
print("\n6. Testing service list...");
|
||||||
|
try {
|
||||||
|
let services = list(manager);
|
||||||
|
print("✓ Service list retrieved");
|
||||||
|
|
||||||
|
// Skip service search due to Rhai type constraints with Vec iteration
|
||||||
|
print(" ⚠️ Skipping service search due to Rhai type constraints");
|
||||||
|
|
||||||
|
test_results["list"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service list failed: ${e}`);
|
||||||
|
test_results["list"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 7: Stop the service
|
||||||
|
print("\n7. Testing service stop...");
|
||||||
|
try {
|
||||||
|
stop(manager, test_service_name);
|
||||||
|
print(`✓ Service stopped: ${test_service_name}`);
|
||||||
|
test_results["stop"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service stop failed: ${e}`);
|
||||||
|
test_results["stop"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 8: Remove the service
|
||||||
|
print("\n8. Testing service remove...");
|
||||||
|
try {
|
||||||
|
remove(manager, test_service_name);
|
||||||
|
print(`✓ Service removed: ${test_service_name}`);
|
||||||
|
test_results["remove"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Service remove failed: ${e}`);
|
||||||
|
test_results["remove"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test 9: Verify cleanup
|
||||||
|
print("\n9. Testing cleanup verification...");
|
||||||
|
try {
|
||||||
|
let exists_after_remove = exists(manager, test_service_name);
|
||||||
|
if !exists_after_remove {
|
||||||
|
print("✓ Service correctly doesn't exist after removal");
|
||||||
|
test_results["cleanup"] = "PASS";
|
||||||
|
passed_tests += 1;
|
||||||
|
} else {
|
||||||
|
print("✗ Service still exists after removal");
|
||||||
|
test_results["cleanup"] = "FAIL";
|
||||||
|
}
|
||||||
|
total_tests += 1;
|
||||||
|
} catch(e) {
|
||||||
|
print(`✗ Cleanup verification failed: ${e}`);
|
||||||
|
test_results["cleanup"] = "FAIL";
|
||||||
|
total_tests += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Summary
|
||||||
|
print("\n=== Test Summary ===");
|
||||||
|
print(`Total tests: ${total_tests}`);
|
||||||
|
print(`Passed: ${passed_tests}`);
|
||||||
|
print(`Failed: ${total_tests - passed_tests}`);
|
||||||
|
print(`Success rate: ${(passed_tests * 100) / total_tests}%`);
|
||||||
|
|
||||||
|
print("\nDetailed Results:");
|
||||||
|
for test_name in test_results.keys() {
|
||||||
|
let result = test_results[test_name];
|
||||||
|
let status_icon = if result == "PASS" { "✓" } else if result == "FAIL" { "✗" } else { "⚠" };
|
||||||
|
print(` ${status_icon} ${test_name}: ${result}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if passed_tests == total_tests {
|
||||||
|
print("\n🎉 All tests passed!");
|
||||||
|
} else {
|
||||||
|
print(`\n⚠ ${total_tests - passed_tests} test(s) failed`);
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n=== Service Manager Basic Test Complete ===");
|
||||||
|
|
||||||
|
// Return test results for potential use by calling code
|
||||||
|
test_results
|
252
_archive/service_manager/tests/rhai_integration_tests.rs
Normal file
252
_archive/service_manager/tests/rhai_integration_tests.rs
Normal file
@@ -0,0 +1,252 @@
|
|||||||
|
use rhai::{Engine, EvalAltResult};
|
||||||
|
use std::fs;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// Helper function to create a Rhai engine for service manager testing
|
||||||
|
fn create_service_manager_engine() -> Result<Engine, Box<EvalAltResult>> {
|
||||||
|
#[cfg(feature = "rhai")]
|
||||||
|
{
|
||||||
|
let mut engine = Engine::new();
|
||||||
|
// Register the service manager module for real testing
|
||||||
|
sal_service_manager::rhai::register_service_manager_module(&mut engine)?;
|
||||||
|
Ok(engine)
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "rhai"))]
|
||||||
|
{
|
||||||
|
Ok(Engine::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to run a Rhai script file
|
||||||
|
fn run_rhai_script(script_path: &str) -> Result<rhai::Dynamic, Box<EvalAltResult>> {
|
||||||
|
let engine = create_service_manager_engine()?;
|
||||||
|
|
||||||
|
// Read the script file
|
||||||
|
let script_content = fs::read_to_string(script_path)
|
||||||
|
.map_err(|e| format!("Failed to read script file {}: {}", script_path, e))?;
|
||||||
|
|
||||||
|
// Execute the script
|
||||||
|
engine.eval::<rhai::Dynamic>(&script_content)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rhai_service_manager_basic() {
|
||||||
|
let script_path = "tests/rhai/service_manager_basic.rhai";
|
||||||
|
|
||||||
|
if !Path::new(script_path).exists() {
|
||||||
|
println!("⚠ Skipping test: Rhai script not found at {}", script_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Running Rhai service manager basic test...");
|
||||||
|
|
||||||
|
match run_rhai_script(script_path) {
|
||||||
|
Ok(result) => {
|
||||||
|
println!("✓ Rhai basic test completed successfully");
|
||||||
|
|
||||||
|
// Try to extract test results if the script returns them
|
||||||
|
if let Some(map) = result.try_cast::<rhai::Map>() {
|
||||||
|
println!("Test results received from Rhai script:");
|
||||||
|
for (key, value) in map.iter() {
|
||||||
|
println!(" {}: {:?}", key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all tests passed
|
||||||
|
let all_passed = map.values().all(|v| {
|
||||||
|
if let Some(s) = v.clone().try_cast::<String>() {
|
||||||
|
s == "PASS"
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if all_passed {
|
||||||
|
println!("✓ All Rhai tests reported as PASS");
|
||||||
|
} else {
|
||||||
|
println!("⚠ Some Rhai tests did not pass");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✗ Rhai basic test failed: {}", e);
|
||||||
|
assert!(false, "Rhai script execution failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rhai_service_lifecycle() {
|
||||||
|
let script_path = "tests/rhai/service_lifecycle.rhai";
|
||||||
|
|
||||||
|
if !Path::new(script_path).exists() {
|
||||||
|
println!("⚠ Skipping test: Rhai script not found at {}", script_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("Running Rhai service lifecycle test...");
|
||||||
|
|
||||||
|
match run_rhai_script(script_path) {
|
||||||
|
Ok(result) => {
|
||||||
|
println!("✓ Rhai lifecycle test completed successfully");
|
||||||
|
|
||||||
|
// Try to extract test results if the script returns them
|
||||||
|
if let Some(map) = result.try_cast::<rhai::Map>() {
|
||||||
|
println!("Lifecycle test results received from Rhai script:");
|
||||||
|
|
||||||
|
// Extract summary if available
|
||||||
|
if let Some(summary) = map.get("summary") {
|
||||||
|
if let Some(summary_map) = summary.clone().try_cast::<rhai::Map>() {
|
||||||
|
println!("Summary:");
|
||||||
|
for (key, value) in summary_map.iter() {
|
||||||
|
println!(" {}: {:?}", key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract performance metrics if available
|
||||||
|
if let Some(performance) = map.get("performance") {
|
||||||
|
if let Some(perf_map) = performance.clone().try_cast::<rhai::Map>() {
|
||||||
|
println!("Performance:");
|
||||||
|
for (key, value) in perf_map.iter() {
|
||||||
|
println!(" {}: {:?}", key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✗ Rhai lifecycle test failed: {}", e);
|
||||||
|
assert!(false, "Rhai script execution failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rhai_engine_functionality() {
|
||||||
|
println!("Testing basic Rhai engine functionality...");
|
||||||
|
|
||||||
|
let engine = create_service_manager_engine().expect("Failed to create Rhai engine");
|
||||||
|
|
||||||
|
// Test basic Rhai functionality
|
||||||
|
let test_script = r#"
|
||||||
|
let test_results = #{
|
||||||
|
basic_math: 2 + 2 == 4,
|
||||||
|
string_ops: "hello".len() == 5,
|
||||||
|
array_ops: [1, 2, 3].len() == 3,
|
||||||
|
map_ops: #{ a: 1, b: 2 }.len() == 2
|
||||||
|
};
|
||||||
|
|
||||||
|
let all_passed = true;
|
||||||
|
for result in test_results.values() {
|
||||||
|
if !result {
|
||||||
|
all_passed = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#{
|
||||||
|
results: test_results,
|
||||||
|
all_passed: all_passed
|
||||||
|
}
|
||||||
|
"#;
|
||||||
|
|
||||||
|
match engine.eval::<rhai::Dynamic>(test_script) {
|
||||||
|
Ok(result) => {
|
||||||
|
if let Some(map) = result.try_cast::<rhai::Map>() {
|
||||||
|
if let Some(all_passed) = map.get("all_passed") {
|
||||||
|
if let Some(passed) = all_passed.clone().try_cast::<bool>() {
|
||||||
|
if passed {
|
||||||
|
println!("✓ All basic Rhai functionality tests passed");
|
||||||
|
} else {
|
||||||
|
println!("✗ Some basic Rhai functionality tests failed");
|
||||||
|
assert!(false, "Basic Rhai tests failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(results) = map.get("results") {
|
||||||
|
if let Some(results_map) = results.clone().try_cast::<rhai::Map>() {
|
||||||
|
println!("Detailed results:");
|
||||||
|
for (test_name, result) in results_map.iter() {
|
||||||
|
let status = if let Some(passed) = result.clone().try_cast::<bool>() {
|
||||||
|
if passed {
|
||||||
|
"✓"
|
||||||
|
} else {
|
||||||
|
"✗"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"?"
|
||||||
|
};
|
||||||
|
println!(" {} {}: {:?}", status, test_name, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✗ Basic Rhai functionality test failed: {}", e);
|
||||||
|
assert!(false, "Basic Rhai test failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rhai_script_error_handling() {
|
||||||
|
println!("Testing Rhai error handling...");
|
||||||
|
|
||||||
|
let engine = create_service_manager_engine().expect("Failed to create Rhai engine");
|
||||||
|
|
||||||
|
// Test script with intentional error
|
||||||
|
let error_script = r#"
|
||||||
|
let result = "test";
|
||||||
|
result.non_existent_method(); // This should cause an error
|
||||||
|
"#;
|
||||||
|
|
||||||
|
match engine.eval::<rhai::Dynamic>(error_script) {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("⚠ Expected error but script succeeded");
|
||||||
|
assert!(
|
||||||
|
false,
|
||||||
|
"Error handling test failed - expected error but got success"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("✓ Error correctly caught: {}", e);
|
||||||
|
// Verify it's the expected type of error
|
||||||
|
assert!(e.to_string().contains("method") || e.to_string().contains("function"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_rhai_script_files_exist() {
|
||||||
|
println!("Checking that Rhai test scripts exist...");
|
||||||
|
|
||||||
|
let script_files = [
|
||||||
|
"tests/rhai/service_manager_basic.rhai",
|
||||||
|
"tests/rhai/service_lifecycle.rhai",
|
||||||
|
];
|
||||||
|
|
||||||
|
for script_file in &script_files {
|
||||||
|
if Path::new(script_file).exists() {
|
||||||
|
println!("✓ Found script: {}", script_file);
|
||||||
|
|
||||||
|
// Verify the file is readable and not empty
|
||||||
|
match fs::read_to_string(script_file) {
|
||||||
|
Ok(content) => {
|
||||||
|
if content.trim().is_empty() {
|
||||||
|
assert!(false, "Script file {} is empty", script_file);
|
||||||
|
}
|
||||||
|
println!(" Content length: {} characters", content.len());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
assert!(false, "Failed to read script file {}: {}", script_file, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
assert!(false, "Required script file not found: {}", script_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✓ All required Rhai script files exist and are readable");
|
||||||
|
}
|
317
_archive/service_manager/tests/zinit_integration_tests.rs
Normal file
317
_archive/service_manager/tests/zinit_integration_tests.rs
Normal file
@@ -0,0 +1,317 @@
|
|||||||
|
use sal_service_manager::{
|
||||||
|
ServiceConfig, ServiceManager, ServiceManagerError, ServiceStatus, ZinitServiceManager,
|
||||||
|
};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::time::sleep;
|
||||||
|
|
||||||
|
/// Helper function to find an available Zinit socket path
|
||||||
|
async fn get_available_socket_path() -> Option<String> {
|
||||||
|
let socket_paths = [
|
||||||
|
"/var/run/zinit.sock",
|
||||||
|
"/tmp/zinit.sock",
|
||||||
|
"/run/zinit.sock",
|
||||||
|
"./zinit.sock",
|
||||||
|
];
|
||||||
|
|
||||||
|
for path in &socket_paths {
|
||||||
|
// Try to create a ZinitServiceManager to test connectivity
|
||||||
|
if let Ok(manager) = ZinitServiceManager::new(path) {
|
||||||
|
// Test if we can list services (basic connectivity test)
|
||||||
|
if manager.list().is_ok() {
|
||||||
|
println!("✓ Found working Zinit socket at: {}", path);
|
||||||
|
return Some(path.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper function to clean up test services
|
||||||
|
async fn cleanup_test_service(manager: &dyn ServiceManager, service_name: &str) {
|
||||||
|
let _ = manager.stop(service_name);
|
||||||
|
let _ = manager.remove(service_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_zinit_service_manager_creation() {
|
||||||
|
if let Some(socket_path) = get_available_socket_path().await {
|
||||||
|
let manager = ZinitServiceManager::new(&socket_path);
|
||||||
|
assert!(
|
||||||
|
manager.is_ok(),
|
||||||
|
"Should be able to create ZinitServiceManager"
|
||||||
|
);
|
||||||
|
|
||||||
|
let manager = manager.unwrap();
|
||||||
|
|
||||||
|
// Test basic connectivity by listing services
|
||||||
|
let list_result = manager.list();
|
||||||
|
assert!(list_result.is_ok(), "Should be able to list services");
|
||||||
|
|
||||||
|
println!("✓ ZinitServiceManager created successfully");
|
||||||
|
} else {
|
||||||
|
println!("⚠ Skipping test_zinit_service_manager_creation: No Zinit socket available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_service_lifecycle() {
|
||||||
|
if let Some(socket_path) = get_available_socket_path().await {
|
||||||
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||||
|
let service_name = "test-lifecycle-service";
|
||||||
|
|
||||||
|
// Clean up any existing service first
|
||||||
|
cleanup_test_service(&manager, service_name).await;
|
||||||
|
|
||||||
|
let config = ServiceConfig {
|
||||||
|
name: service_name.to_string(),
|
||||||
|
binary_path: "echo".to_string(),
|
||||||
|
args: vec!["Hello from lifecycle test".to_string()],
|
||||||
|
working_directory: Some("/tmp".to_string()),
|
||||||
|
environment: HashMap::new(),
|
||||||
|
auto_restart: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test service creation and start
|
||||||
|
println!("Testing service creation and start...");
|
||||||
|
let start_result = manager.start(&config);
|
||||||
|
match start_result {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("✓ Service started successfully");
|
||||||
|
|
||||||
|
// Wait a bit for the service to run
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
|
|
||||||
|
// Test service exists
|
||||||
|
let exists = manager.exists(service_name);
|
||||||
|
assert!(exists.is_ok(), "Should be able to check if service exists");
|
||||||
|
|
||||||
|
if let Ok(true) = exists {
|
||||||
|
println!("✓ Service exists check passed");
|
||||||
|
|
||||||
|
// Test service status
|
||||||
|
let status_result = manager.status(service_name);
|
||||||
|
match status_result {
|
||||||
|
Ok(status) => {
|
||||||
|
println!("✓ Service status: {:?}", status);
|
||||||
|
assert!(
|
||||||
|
matches!(status, ServiceStatus::Running | ServiceStatus::Stopped),
|
||||||
|
"Service should be running or stopped (for oneshot)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => println!("⚠ Status check failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test service logs
|
||||||
|
let logs_result = manager.logs(service_name, None);
|
||||||
|
match logs_result {
|
||||||
|
Ok(logs) => {
|
||||||
|
println!("✓ Retrieved logs: {}", logs.len());
|
||||||
|
// For echo command, we should have some output
|
||||||
|
assert!(
|
||||||
|
!logs.is_empty() || logs.contains("Hello"),
|
||||||
|
"Should have log output"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => println!("⚠ Logs retrieval failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test service list
|
||||||
|
let list_result = manager.list();
|
||||||
|
match list_result {
|
||||||
|
Ok(services) => {
|
||||||
|
println!("✓ Listed {} services", services.len());
|
||||||
|
assert!(
|
||||||
|
services.contains(&service_name.to_string()),
|
||||||
|
"Service should appear in list"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Err(e) => println!("⚠ List services failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test service stop
|
||||||
|
println!("Testing service stop...");
|
||||||
|
let stop_result = manager.stop(service_name);
|
||||||
|
match stop_result {
|
||||||
|
Ok(_) => println!("✓ Service stopped successfully"),
|
||||||
|
Err(e) => println!("⚠ Stop failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test service removal
|
||||||
|
println!("Testing service removal...");
|
||||||
|
let remove_result = manager.remove(service_name);
|
||||||
|
match remove_result {
|
||||||
|
Ok(_) => println!("✓ Service removed successfully"),
|
||||||
|
Err(e) => println!("⚠ Remove failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("⚠ Service creation/start failed: {}", e);
|
||||||
|
// This might be expected if zinit doesn't allow service creation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Final cleanup
|
||||||
|
cleanup_test_service(&manager, service_name).await;
|
||||||
|
} else {
|
||||||
|
println!("⚠ Skipping test_service_lifecycle: No Zinit socket available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_service_start_and_confirm() {
|
||||||
|
if let Some(socket_path) = get_available_socket_path().await {
|
||||||
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||||
|
let service_name = "test-start-confirm-service";
|
||||||
|
|
||||||
|
// Clean up any existing service first
|
||||||
|
cleanup_test_service(&manager, service_name).await;
|
||||||
|
|
||||||
|
let config = ServiceConfig {
|
||||||
|
name: service_name.to_string(),
|
||||||
|
binary_path: "sleep".to_string(),
|
||||||
|
args: vec!["5".to_string()], // Sleep for 5 seconds
|
||||||
|
working_directory: Some("/tmp".to_string()),
|
||||||
|
environment: HashMap::new(),
|
||||||
|
auto_restart: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test start_and_confirm with timeout
|
||||||
|
println!("Testing start_and_confirm with timeout...");
|
||||||
|
let start_result = manager.start_and_confirm(&config, 10);
|
||||||
|
match start_result {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("✓ Service started and confirmed successfully");
|
||||||
|
|
||||||
|
// Verify it's actually running
|
||||||
|
let status = manager.status(service_name);
|
||||||
|
match status {
|
||||||
|
Ok(ServiceStatus::Running) => println!("✓ Service confirmed running"),
|
||||||
|
Ok(other_status) => {
|
||||||
|
println!("⚠ Service in unexpected state: {:?}", other_status)
|
||||||
|
}
|
||||||
|
Err(e) => println!("⚠ Status check failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("⚠ start_and_confirm failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test start_existing_and_confirm
|
||||||
|
println!("Testing start_existing_and_confirm...");
|
||||||
|
let start_existing_result = manager.start_existing_and_confirm(service_name, 5);
|
||||||
|
match start_existing_result {
|
||||||
|
Ok(_) => println!("✓ start_existing_and_confirm succeeded"),
|
||||||
|
Err(e) => println!("⚠ start_existing_and_confirm failed: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanup_test_service(&manager, service_name).await;
|
||||||
|
} else {
|
||||||
|
println!("⚠ Skipping test_service_start_and_confirm: No Zinit socket available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_service_restart() {
|
||||||
|
if let Some(socket_path) = get_available_socket_path().await {
|
||||||
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||||
|
let service_name = "test-restart-service";
|
||||||
|
|
||||||
|
// Clean up any existing service first
|
||||||
|
cleanup_test_service(&manager, service_name).await;
|
||||||
|
|
||||||
|
let config = ServiceConfig {
|
||||||
|
name: service_name.to_string(),
|
||||||
|
binary_path: "echo".to_string(),
|
||||||
|
args: vec!["Restart test".to_string()],
|
||||||
|
working_directory: Some("/tmp".to_string()),
|
||||||
|
environment: HashMap::new(),
|
||||||
|
auto_restart: true, // Enable auto-restart for this test
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start the service first
|
||||||
|
let start_result = manager.start(&config);
|
||||||
|
if start_result.is_ok() {
|
||||||
|
// Wait for service to be established
|
||||||
|
sleep(Duration::from_millis(1000)).await;
|
||||||
|
|
||||||
|
// Test restart
|
||||||
|
println!("Testing service restart...");
|
||||||
|
let restart_result = manager.restart(service_name);
|
||||||
|
match restart_result {
|
||||||
|
Ok(_) => {
|
||||||
|
println!("✓ Service restarted successfully");
|
||||||
|
|
||||||
|
// Wait and check status
|
||||||
|
sleep(Duration::from_millis(500)).await;
|
||||||
|
|
||||||
|
let status_result = manager.status(service_name);
|
||||||
|
match status_result {
|
||||||
|
Ok(status) => {
|
||||||
|
println!("✓ Service state after restart: {:?}", status);
|
||||||
|
}
|
||||||
|
Err(e) => println!("⚠ Status check after restart failed: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
println!("⚠ Restart failed: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
cleanup_test_service(&manager, service_name).await;
|
||||||
|
} else {
|
||||||
|
println!("⚠ Skipping test_service_restart: No Zinit socket available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_error_handling() {
|
||||||
|
if let Some(socket_path) = get_available_socket_path().await {
|
||||||
|
let manager = ZinitServiceManager::new(&socket_path).expect("Failed to create manager");
|
||||||
|
|
||||||
|
// Test operations on non-existent service
|
||||||
|
let non_existent_service = "non-existent-service-12345";
|
||||||
|
|
||||||
|
// Test status of non-existent service
|
||||||
|
let status_result = manager.status(non_existent_service);
|
||||||
|
match status_result {
|
||||||
|
Err(ServiceManagerError::ServiceNotFound(_)) => {
|
||||||
|
println!("✓ Correctly returned ServiceNotFound for non-existent service");
|
||||||
|
}
|
||||||
|
Err(other_error) => {
|
||||||
|
println!(
|
||||||
|
"⚠ Got different error for non-existent service: {}",
|
||||||
|
other_error
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(_) => {
|
||||||
|
println!("⚠ Unexpectedly found non-existent service");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test exists for non-existent service
|
||||||
|
let exists_result = manager.exists(non_existent_service);
|
||||||
|
match exists_result {
|
||||||
|
Ok(false) => println!("✓ Correctly reported non-existent service as not existing"),
|
||||||
|
Ok(true) => println!("⚠ Incorrectly reported non-existent service as existing"),
|
||||||
|
Err(e) => println!("⚠ Error checking existence: {}", e),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test stop of non-existent service
|
||||||
|
let stop_result = manager.stop(non_existent_service);
|
||||||
|
match stop_result {
|
||||||
|
Err(_) => println!("✓ Correctly failed to stop non-existent service"),
|
||||||
|
Ok(_) => println!("⚠ Unexpectedly succeeded in stopping non-existent service"),
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("✓ Error handling tests completed");
|
||||||
|
} else {
|
||||||
|
println!("⚠ Skipping test_error_handling: No Zinit socket available");
|
||||||
|
}
|
||||||
|
}
|
@@ -1,14 +0,0 @@
|
|||||||
# Environment Configuration
|
|
||||||
|
|
||||||
To set up your environment variables:
|
|
||||||
|
|
||||||
1. Copy the template file to `env.sh`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp config/myenv_templ.sh config/env.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Edit `config/env.sh` and fill in your specific values for the variables.
|
|
||||||
|
|
||||||
3. This file (`config/env.sh`) is excluded from version control by the project's `.gitignore` configuration, ensuring your sensitive information remains local and is never committed to the repository.
|
|
||||||
|
|
@@ -1,6 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
export OPENROUTER_API_KEY=""
|
|
||||||
export GROQ_API_KEY=""
|
|
||||||
export CEREBRAS_API_KEY=""
|
|
||||||
export OPENAI_API_KEY="sk-xxxxxxx"
|
|
@@ -1,43 +0,0 @@
|
|||||||
# RFS Client Rhai Examples
|
|
||||||
|
|
||||||
This folder contains Rhai examples that use the SAL RFS client wrappers registered by `sal::rhai::register(&mut engine)` and executed by the `herodo` binary.
|
|
||||||
|
|
||||||
## Quick start
|
|
||||||
|
|
||||||
Run the auth + upload + download example (uses hardcoded credentials and `/etc/hosts` as input):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo run -p herodo -- examples/rfsclient/auth_and_upload.rhai
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, the script:
|
|
||||||
|
|
||||||
- Uses base URL `http://127.0.0.1:8080`
|
|
||||||
- Uses credentials `user` / `password`
|
|
||||||
- Uploads the file `/etc/hosts`
|
|
||||||
- Downloads to `/tmp/rfs_example_out.txt`
|
|
||||||
|
|
||||||
To customize, edit `examples/rfsclient/auth_and_upload.rhai` near the top and change `BASE_URL`, `USER`, `PASS`, and file paths.
|
|
||||||
|
|
||||||
## What the example does
|
|
||||||
|
|
||||||
- Creates the RFS client: `rfs_create_client(BASE_URL, USER, PASS, TIMEOUT)`
|
|
||||||
- Health check: `rfs_health_check()`
|
|
||||||
- Authenticates: `rfs_authenticate()`
|
|
||||||
- Uploads a file: `rfs_upload_file(local_path, chunk_size, verify)` → returns file hash
|
|
||||||
- Downloads it back: `rfs_download_file(file_id_or_hash, dest_path, verify)` → returns unit (throws on error)
|
|
||||||
|
|
||||||
See `examples/rfsclient/auth_and_upload.rhai` for details.
|
|
||||||
|
|
||||||
## Using the Rust client directly (optional)
|
|
||||||
|
|
||||||
If you want to use the Rust API (without Rhai), depend on `sal-rfs-client` and see:
|
|
||||||
|
|
||||||
- `packages/clients/rfsclient/src/client.rs` (`RfsClient`)
|
|
||||||
- `packages/clients/rfsclient/src/types.rs` (config and option types)
|
|
||||||
- `packages/clients/rfsclient/examples/` (example usage)
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
- Auth failures: verify credentials and that the server requires/authenticates them.
|
|
||||||
- Connection errors: verify the base URL is reachable from your machine.
|
|
@@ -1,41 +0,0 @@
|
|||||||
// RFS Client: Auth + Upload + Download example
|
|
||||||
// Prereqs:
|
|
||||||
// - RFS server reachable at RFS_BASE_URL
|
|
||||||
// - Valid credentials in env: RFS_USER, RFS_PASS
|
|
||||||
// - Run with herodo so the SAL Rhai modules are registered
|
|
||||||
|
|
||||||
// NOTE: env_get not available in this runtime; hardcode or replace with your env loader
|
|
||||||
let BASE_URL = "http://127.0.0.1:8080";
|
|
||||||
let USER = "user";
|
|
||||||
let PASS = "password";
|
|
||||||
let TIMEOUT = 30; // seconds
|
|
||||||
|
|
||||||
if BASE_URL == "" { throw "Set BASE_URL in the script"; }
|
|
||||||
|
|
||||||
// Create client
|
|
||||||
let ok = rfs_create_client(BASE_URL, USER, PASS, TIMEOUT);
|
|
||||||
if !ok { throw "Failed to create RFS client"; }
|
|
||||||
|
|
||||||
// Optional health check
|
|
||||||
let health = rfs_health_check();
|
|
||||||
print(`RFS health: ${health}`);
|
|
||||||
|
|
||||||
// Authenticate (required for some operations)
|
|
||||||
let auth_ok = rfs_authenticate();
|
|
||||||
if !auth_ok { throw "Authentication failed"; }
|
|
||||||
|
|
||||||
// Upload a local file
|
|
||||||
// Use an existing readable file to avoid needing os_write_file module
|
|
||||||
let local_file = "/etc/hosts";
|
|
||||||
// rfs_upload_file(file_path, chunk_size, verify)
|
|
||||||
let hash = rfs_upload_file(local_file, 0, false);
|
|
||||||
print(`Uploaded file hash: ${hash}`);
|
|
||||||
|
|
||||||
// Download it back
|
|
||||||
let out_path = "/tmp/rfs_example_out.txt";
|
|
||||||
// rfs_download_file(file_id, output_path, verify) returns unit and throws on error
|
|
||||||
rfs_download_file(hash, out_path, false);
|
|
||||||
|
|
||||||
print(`Downloaded to: ${out_path}`);
|
|
||||||
|
|
||||||
true
|
|
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "openrouter_example"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[workspace]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "openrouter_example"
|
|
||||||
path = "openrouter_example.rs"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
codemonkey = { path = "../../packages/ai/codemonkey" }
|
|
||||||
openai-api-rs = "6.0.8"
|
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
|
@@ -1,47 +0,0 @@
|
|||||||
use codemonkey::{create_ai_provider, AIProviderType, CompletionRequestBuilder, Message, MessageRole, Content};
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
|
|
||||||
let (mut provider, provider_type) = create_ai_provider(AIProviderType::OpenRouter)?;
|
|
||||||
|
|
||||||
let messages = vec![Message {
|
|
||||||
role: MessageRole::user,
|
|
||||||
content: Content::Text("Explain the concept of a factory design pattern in Rust.".to_string()),
|
|
||||||
name: None,
|
|
||||||
tool_calls: None,
|
|
||||||
tool_call_id: None,
|
|
||||||
}];
|
|
||||||
|
|
||||||
println!("Sending request to OpenRouter...");
|
|
||||||
let response = CompletionRequestBuilder::new(
|
|
||||||
&mut *provider,
|
|
||||||
"openai/gpt-oss-120b".to_string(), // Model name as specified by the user
|
|
||||||
messages,
|
|
||||||
provider_type, // Pass the provider_type
|
|
||||||
)
|
|
||||||
.temperature(1.0)
|
|
||||||
.max_tokens(8192)
|
|
||||||
.top_p(1.0)
|
|
||||||
.reasoning_effort("medium")
|
|
||||||
.stream(false)
|
|
||||||
.openrouter_options(|builder| {
|
|
||||||
builder.provider(
|
|
||||||
codemonkey::OpenRouterProviderOptionsBuilder::new()
|
|
||||||
.order(vec!["cerebras"])
|
|
||||||
.build(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.completion()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
for choice in response.choices {
|
|
||||||
if let Some(content) = choice.message.content {
|
|
||||||
print!("{}", content);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@@ -1,13 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
# Change to directory where this script is located
|
|
||||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
|
||||||
|
|
||||||
source ../../config/myenv.sh
|
|
||||||
|
|
||||||
# Build the example
|
|
||||||
cargo build
|
|
||||||
|
|
||||||
# Run the example
|
|
||||||
cargo run --bin openrouter_example
|
|
@@ -1,10 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "codemonkey"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
|
||||||
async-trait = "0.1.80"
|
|
||||||
openrouter-rs = "0.4.5"
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
@@ -1,216 +0,0 @@
|
|||||||
use async_trait::async_trait;
|
|
||||||
use openrouter_rs::{OpenRouterClient, api::chat::{ChatCompletionRequest, Message}, types::completion::CompletionsResponse};
|
|
||||||
use std::env;
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
// Re-export MessageRole for easier use in client code
|
|
||||||
pub use openrouter_rs::types::Role as MessageRole;
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait AIProvider {
|
|
||||||
async fn completion(
|
|
||||||
&mut self,
|
|
||||||
request: CompletionRequest,
|
|
||||||
) -> Result<CompletionsResponse, Box<dyn Error>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CompletionRequest {
|
|
||||||
pub model: String,
|
|
||||||
pub messages: Vec<Message>,
|
|
||||||
pub temperature: Option<f64>,
|
|
||||||
pub max_tokens: Option<i64>,
|
|
||||||
pub top_p: Option<f64>,
|
|
||||||
pub stream: Option<bool>,
|
|
||||||
pub stop: Option<Vec<String>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CompletionRequestBuilder<'a> {
|
|
||||||
provider: &'a mut dyn AIProvider,
|
|
||||||
model: String,
|
|
||||||
messages: Vec<Message>,
|
|
||||||
temperature: Option<f64>,
|
|
||||||
max_tokens: Option<i64>,
|
|
||||||
top_p: Option<f64>,
|
|
||||||
stream: Option<bool>,
|
|
||||||
stop: Option<Vec<String>>,
|
|
||||||
provider_type: AIProviderType,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> CompletionRequestBuilder<'a> {
|
|
||||||
pub fn new(provider: &'a mut dyn AIProvider, model: String, messages: Vec<Message>, provider_type: AIProviderType) -> Self {
|
|
||||||
Self {
|
|
||||||
provider,
|
|
||||||
model,
|
|
||||||
messages,
|
|
||||||
temperature: None,
|
|
||||||
max_tokens: None,
|
|
||||||
top_p: None,
|
|
||||||
stream: None,
|
|
||||||
stop: None,
|
|
||||||
provider_type,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn temperature(mut self, temperature: f64) -> Self {
|
|
||||||
self.temperature = Some(temperature);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max_tokens(mut self, max_tokens: i64) -> Self {
|
|
||||||
self.max_tokens = Some(max_tokens);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn top_p(mut self, top_p: f64) -> Self {
|
|
||||||
self.top_p = Some(top_p);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stream(mut self, stream: bool) -> Self {
|
|
||||||
self.stream = Some(stream);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(mut self, stop: Vec<String>) -> Self {
|
|
||||||
self.stop = Some(stop);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn completion(self) -> Result<CompletionsResponse, Box<dyn Error>> {
|
|
||||||
let request = CompletionRequest {
|
|
||||||
model: self.model,
|
|
||||||
messages: self.messages,
|
|
||||||
temperature: self.temperature,
|
|
||||||
max_tokens: self.max_tokens,
|
|
||||||
top_p: self.top_p,
|
|
||||||
stream: self.stream,
|
|
||||||
stop: self.stop,
|
|
||||||
};
|
|
||||||
self.provider.completion(request).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GroqAIProvider {
|
|
||||||
client: OpenRouterClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AIProvider for GroqAIProvider {
|
|
||||||
async fn completion(
|
|
||||||
&mut self,
|
|
||||||
request: CompletionRequest,
|
|
||||||
) -> Result<CompletionsResponse, Box<dyn Error>> {
|
|
||||||
let chat_request = ChatCompletionRequest::builder()
|
|
||||||
.model(request.model)
|
|
||||||
.messages(request.messages)
|
|
||||||
.temperature(request.temperature.unwrap_or(1.0))
|
|
||||||
.max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048))
|
|
||||||
.top_p(request.top_p.unwrap_or(1.0))
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let result = self.client.send_chat_completion(&chat_request).await?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OpenAIProvider {
|
|
||||||
client: OpenRouterClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AIProvider for OpenAIProvider {
|
|
||||||
async fn completion(
|
|
||||||
&mut self,
|
|
||||||
request: CompletionRequest,
|
|
||||||
) -> Result<CompletionsResponse, Box<dyn Error>> {
|
|
||||||
let chat_request = ChatCompletionRequest::builder()
|
|
||||||
.model(request.model)
|
|
||||||
.messages(request.messages)
|
|
||||||
.temperature(request.temperature.unwrap_or(1.0))
|
|
||||||
.max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048))
|
|
||||||
.top_p(request.top_p.unwrap_or(1.0))
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let result = self.client.send_chat_completion(&chat_request).await?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct OpenRouterAIProvider {
|
|
||||||
client: OpenRouterClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AIProvider for OpenRouterAIProvider {
|
|
||||||
async fn completion(
|
|
||||||
&mut self,
|
|
||||||
request: CompletionRequest,
|
|
||||||
) -> Result<CompletionsResponse, Box<dyn Error>> {
|
|
||||||
let chat_request = ChatCompletionRequest::builder()
|
|
||||||
.model(request.model)
|
|
||||||
.messages(request.messages)
|
|
||||||
.temperature(request.temperature.unwrap_or(1.0))
|
|
||||||
.max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048))
|
|
||||||
.top_p(request.top_p.unwrap_or(1.0))
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let result = self.client.send_chat_completion(&chat_request).await?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CerebrasAIProvider {
|
|
||||||
client: OpenRouterClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl AIProvider for CerebrasAIProvider {
|
|
||||||
async fn completion(
|
|
||||||
&mut self,
|
|
||||||
request: CompletionRequest,
|
|
||||||
) -> Result<CompletionsResponse, Box<dyn Error>> {
|
|
||||||
let chat_request = ChatCompletionRequest::builder()
|
|
||||||
.model(request.model)
|
|
||||||
.messages(request.messages)
|
|
||||||
.temperature(request.temperature.unwrap_or(1.0))
|
|
||||||
.max_tokens(request.max_tokens.map(|x| x as u32).unwrap_or(2048))
|
|
||||||
.top_p(request.top_p.unwrap_or(1.0))
|
|
||||||
.build()?;
|
|
||||||
|
|
||||||
let result = self.client.send_chat_completion(&chat_request).await?;
|
|
||||||
Ok(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
|
||||||
pub enum AIProviderType {
|
|
||||||
Groq,
|
|
||||||
OpenAI,
|
|
||||||
OpenRouter,
|
|
||||||
Cerebras,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_ai_provider(provider_type: AIProviderType) -> Result<(Box<dyn AIProvider>, AIProviderType), Box<dyn Error>> {
|
|
||||||
match provider_type {
|
|
||||||
AIProviderType::Groq => {
|
|
||||||
let api_key = env::var("GROQ_API_KEY")?;
|
|
||||||
let client = OpenRouterClient::builder().api_key(api_key).build()?;
|
|
||||||
Ok((Box::new(GroqAIProvider { client }), AIProviderType::Groq))
|
|
||||||
}
|
|
||||||
AIProviderType::OpenAI => {
|
|
||||||
let api_key = env::var("OPENAI_API_KEY")?;
|
|
||||||
let client = OpenRouterClient::builder().api_key(api_key).build()?;
|
|
||||||
Ok((Box::new(OpenAIProvider { client }), AIProviderType::OpenAI))
|
|
||||||
}
|
|
||||||
AIProviderType::OpenRouter => {
|
|
||||||
let api_key = env::var("OPENROUTER_API_KEY")?;
|
|
||||||
let client = OpenRouterClient::builder().api_key(api_key).build()?;
|
|
||||||
Ok((Box::new(OpenRouterAIProvider { client }), AIProviderType::OpenRouter))
|
|
||||||
}
|
|
||||||
AIProviderType::Cerebras => {
|
|
||||||
let api_key = env::var("CEREBRAS_API_KEY")?;
|
|
||||||
let client = OpenRouterClient::builder().api_key(api_key).build()?;
|
|
||||||
Ok((Box::new(CerebrasAIProvider { client }), AIProviderType::Cerebras))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1004,7 +1004,7 @@ impl OrderServerProduct {
|
|||||||
})
|
})
|
||||||
.with_get("traffic", |o: &mut OrderServerProduct| o.traffic.clone())
|
.with_get("traffic", |o: &mut OrderServerProduct| o.traffic.clone())
|
||||||
.with_get("dist", |o: &mut OrderServerProduct| o.dist.clone())
|
.with_get("dist", |o: &mut OrderServerProduct| o.dist.clone())
|
||||||
.with_get("arch", |o: &mut OrderServerProduct| o.dist.clone())
|
.with_get("arch", |o: &mut OrderServerProduct| o.arch.clone())
|
||||||
.with_get("lang", |o: &mut OrderServerProduct| o.lang.clone())
|
.with_get("lang", |o: &mut OrderServerProduct| o.lang.clone())
|
||||||
.with_get("location", |o: &mut OrderServerProduct| o.location.clone())
|
.with_get("location", |o: &mut OrderServerProduct| o.location.clone())
|
||||||
.with_get("prices", |o: &mut OrderServerProduct| o.prices.clone())
|
.with_get("prices", |o: &mut OrderServerProduct| o.prices.clone())
|
||||||
@@ -1027,7 +1027,7 @@ impl fmt::Display for OrderServerProduct {
|
|||||||
table.add_row(row!["Distributions", self.dist.join(", ")]);
|
table.add_row(row!["Distributions", self.dist.join(", ")]);
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
"Architectures",
|
"Architectures",
|
||||||
self.dist.join(", ")
|
self.arch.as_deref().unwrap_or_default().join(", ")
|
||||||
]);
|
]);
|
||||||
table.add_row(row!["Languages", self.lang.join(", ")]);
|
table.add_row(row!["Languages", self.lang.join(", ")]);
|
||||||
table.add_row(row!["Locations", self.location.join(", ")]);
|
table.add_row(row!["Locations", self.location.join(", ")]);
|
||||||
@@ -1270,7 +1270,7 @@ impl AuctionServerProduct {
|
|||||||
})
|
})
|
||||||
.with_get("traffic", |p: &mut AuctionServerProduct| p.traffic.clone())
|
.with_get("traffic", |p: &mut AuctionServerProduct| p.traffic.clone())
|
||||||
.with_get("dist", |p: &mut AuctionServerProduct| p.dist.clone())
|
.with_get("dist", |p: &mut AuctionServerProduct| p.dist.clone())
|
||||||
.with_get("arch", |p: &mut AuctionServerProduct| p.dist.clone())
|
.with_get("arch", |p: &mut AuctionServerProduct| p.arch.clone())
|
||||||
.with_get("lang", |p: &mut AuctionServerProduct| p.lang.clone())
|
.with_get("lang", |p: &mut AuctionServerProduct| p.lang.clone())
|
||||||
.with_get("cpu", |p: &mut AuctionServerProduct| p.cpu.clone())
|
.with_get("cpu", |p: &mut AuctionServerProduct| p.cpu.clone())
|
||||||
.with_get("cpu_benchmark", |p: &mut AuctionServerProduct| {
|
.with_get("cpu_benchmark", |p: &mut AuctionServerProduct| {
|
||||||
@@ -1328,7 +1328,7 @@ impl fmt::Display for AuctionServerProduct {
|
|||||||
table.add_row(row!["Distributions", self.dist.join(", ")]);
|
table.add_row(row!["Distributions", self.dist.join(", ")]);
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
"Architectures",
|
"Architectures",
|
||||||
self.dist.join(", ")
|
self.arch.as_deref().unwrap_or_default().join(", ")
|
||||||
]);
|
]);
|
||||||
table.add_row(row!["Languages", self.lang.join(", ")]);
|
table.add_row(row!["Languages", self.lang.join(", ")]);
|
||||||
table.add_row(row!["CPU", self.cpu.clone()]);
|
table.add_row(row!["CPU", self.cpu.clone()]);
|
||||||
@@ -1486,7 +1486,7 @@ impl fmt::Display for AuctionTransaction {
|
|||||||
table.add_row(row!["Product Distributions", self.product.dist.clone()]);
|
table.add_row(row!["Product Distributions", self.product.dist.clone()]);
|
||||||
table.add_row(row![
|
table.add_row(row![
|
||||||
"Product Architectures",
|
"Product Architectures",
|
||||||
&self.product.dist
|
self.product.arch.as_deref().unwrap_or("N/A")
|
||||||
]);
|
]);
|
||||||
table.add_row(row!["Product Languages", self.product.lang.clone()]);
|
table.add_row(row!["Product Languages", self.product.lang.clone()]);
|
||||||
table.add_row(row!["Product CPU", self.product.cpu.clone()]);
|
table.add_row(row!["Product CPU", self.product.cpu.clone()]);
|
||||||
@@ -1569,7 +1569,7 @@ impl AuctionTransactionProduct {
|
|||||||
})
|
})
|
||||||
.with_get("dist", |p: &mut AuctionTransactionProduct| p.dist.clone())
|
.with_get("dist", |p: &mut AuctionTransactionProduct| p.dist.clone())
|
||||||
.with_get("arch", |p: &mut AuctionTransactionProduct| {
|
.with_get("arch", |p: &mut AuctionTransactionProduct| {
|
||||||
p.dist.clone()
|
p.arch.clone().unwrap_or_default()
|
||||||
})
|
})
|
||||||
.with_get("lang", |p: &mut AuctionTransactionProduct| p.lang.clone())
|
.with_get("lang", |p: &mut AuctionTransactionProduct| p.lang.clone())
|
||||||
.with_get("cpu", |p: &mut AuctionTransactionProduct| p.cpu.clone())
|
.with_get("cpu", |p: &mut AuctionTransactionProduct| p.cpu.clone())
|
||||||
|
@@ -100,7 +100,7 @@ pub fn pretty_print_auction_server_products(products: rhai::Array) {
|
|||||||
product.description.join(", "),
|
product.description.join(", "),
|
||||||
product.traffic,
|
product.traffic,
|
||||||
product.dist.join(", "),
|
product.dist.join(", "),
|
||||||
product.dist.join(", "),
|
product.arch.as_deref().unwrap_or_default().join(", "),
|
||||||
product.lang.join(", "),
|
product.lang.join(", "),
|
||||||
product.cpu,
|
product.cpu,
|
||||||
product.cpu_benchmark,
|
product.cpu_benchmark,
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "sal-rfs-client"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
description = "SAL RFS Client - Client library for Remote File System server"
|
|
||||||
repository = "https://git.threefold.info/herocode/sal"
|
|
||||||
license = "Apache-2.0"
|
|
||||||
keywords = ["rfs", "client", "filesystem", "remote"]
|
|
||||||
categories = ["filesystem", "api-bindings"]
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
openapi = { path = "./openapi" }
|
|
||||||
thiserror.workspace = true
|
|
||||||
url.workspace = true
|
|
||||||
reqwest = { workspace = true, features = ["json", "multipart"] }
|
|
||||||
tokio = { workspace = true, features = ["full"] }
|
|
||||||
serde = { workspace = true, features = ["derive"] }
|
|
||||||
serde_json.workspace = true
|
|
||||||
log.workspace = true
|
|
||||||
bytes.workspace = true
|
|
||||||
futures.workspace = true
|
|
||||||
rhai.workspace = true
|
|
||||||
lazy_static.workspace = true
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
tempfile = "3.0"
|
|
@@ -1,195 +0,0 @@
|
|||||||
# RFS Client
|
|
||||||
|
|
||||||
A Rust client library for interacting with the Remote File System (RFS) server.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This client library provides a user-friendly wrapper around the OpenAPI-generated client code. It offers high-level abstractions for common operations such as:
|
|
||||||
|
|
||||||
- Authentication and session management
|
|
||||||
- File uploads and downloads with progress tracking
|
|
||||||
- Block-level operations and verification
|
|
||||||
- FList creation, monitoring, and management
|
|
||||||
- Timeout configuration and error handling
|
|
||||||
|
|
||||||
## Structure
|
|
||||||
|
|
||||||
The library is organized as follows:
|
|
||||||
|
|
||||||
- `client.rs`: Main client implementation with methods for interacting with the RFS server
|
|
||||||
- `error.rs`: Error types and handling
|
|
||||||
- `types.rs`: Type definitions and utilities
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use rfs_client::RfsClient;
|
|
||||||
use rfs_client::types::{ClientConfig, Credentials};
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a client with custom configuration
|
|
||||||
let config = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: Some(Credentials {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
}),
|
|
||||||
timeout_seconds: 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = RfsClient::new(config);
|
|
||||||
|
|
||||||
// Authenticate
|
|
||||||
client.authenticate().await?;
|
|
||||||
println!("Authentication successful");
|
|
||||||
|
|
||||||
// Upload a file
|
|
||||||
let file_path = "/path/to/file.txt";
|
|
||||||
let file_hash = client.upload_file(file_path, None).await?;
|
|
||||||
println!("File uploaded with hash: {}", file_hash);
|
|
||||||
|
|
||||||
// Download the file
|
|
||||||
let output_path = "/path/to/output.txt";
|
|
||||||
client.download_file(&file_hash, output_path, None).await?;
|
|
||||||
println!("File downloaded to {}", output_path);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Feature Examples
|
|
||||||
|
|
||||||
### Authentication
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create a client with authentication
|
|
||||||
let config = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: Some(Credentials {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
}),
|
|
||||||
timeout_seconds: 30,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = RfsClient::new(config);
|
|
||||||
|
|
||||||
// Authenticate with the server
|
|
||||||
client.authenticate().await?;
|
|
||||||
if client.is_authenticated() {
|
|
||||||
println!("Authentication successful");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### File Management
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Upload a file with options
|
|
||||||
let upload_options = UploadOptions {
|
|
||||||
chunk_size: Some(1024 * 1024), // 1MB chunks
|
|
||||||
verify: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_hash = client.upload_file("/path/to/file.txt", Some(upload_options)).await?;
|
|
||||||
|
|
||||||
// Download the file
|
|
||||||
let download_options = DownloadOptions {
|
|
||||||
verify: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
client.download_file(&file_hash, "/path/to/output.txt", Some(download_options)).await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### FList Operations
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// Create an FList from a Docker image
|
|
||||||
let options = FlistOptions {
|
|
||||||
auth: None,
|
|
||||||
username: None,
|
|
||||||
password: None,
|
|
||||||
email: None,
|
|
||||||
server_address: Some("docker.io".to_string()),
|
|
||||||
identity_token: None,
|
|
||||||
registry_token: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let job_id = client.create_flist("alpine:latest", Some(options)).await?;
|
|
||||||
|
|
||||||
// Wait for FList creation with progress tracking
|
|
||||||
let wait_options = WaitOptions {
|
|
||||||
timeout_seconds: 60,
|
|
||||||
poll_interval_ms: 1000,
|
|
||||||
progress_callback: Some(Box::new(|state| {
|
|
||||||
println!("Progress: FList state is now {:?}", state);
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
let final_state = client.wait_for_flist_creation(&job_id, Some(wait_options)).await?;
|
|
||||||
|
|
||||||
// List available FLists
|
|
||||||
let flists = client.list_flists().await?;
|
|
||||||
|
|
||||||
// Preview an FList
|
|
||||||
let preview = client.preview_flist("flists/user/alpine-latest.fl").await?;
|
|
||||||
|
|
||||||
// Download an FList
|
|
||||||
client.download_flist("flists/user/alpine-latest.fl", "/tmp/downloaded_flist.fl").await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Block Management
|
|
||||||
|
|
||||||
```rust
|
|
||||||
// List blocks
|
|
||||||
let blocks_list = client.list_blocks(None).await?;
|
|
||||||
|
|
||||||
// Check if a block exists
|
|
||||||
let exists = client.check_block("block_hash").await?;
|
|
||||||
|
|
||||||
// Get block content
|
|
||||||
let block_content = client.get_block("block_hash").await?;
|
|
||||||
|
|
||||||
// Upload a block
|
|
||||||
let block_hash = client.upload_block("file_hash", 0, data).await?;
|
|
||||||
|
|
||||||
// Verify blocks
|
|
||||||
let request = VerifyBlocksRequest { blocks: verify_blocks };
|
|
||||||
let verify_result = client.verify_blocks(request).await?;
|
|
||||||
```
|
|
||||||
|
|
||||||
## Complete Examples
|
|
||||||
|
|
||||||
For more detailed examples, check the `examples` directory:
|
|
||||||
|
|
||||||
- `authentication.rs`: Authentication and health check examples
|
|
||||||
- `file_management.rs`: File upload and download with verification
|
|
||||||
- `flist_operations.rs`: Complete FList creation, monitoring, listing, preview, and download
|
|
||||||
- `block_management.rs`: Block-level operations including listing, verification, and upload
|
|
||||||
- `wait_for_flist.rs`: Advanced FList creation with progress monitoring
|
|
||||||
|
|
||||||
Run an example with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo run --example flist_operations
|
|
||||||
```
|
|
||||||
|
|
||||||
## Development
|
|
||||||
|
|
||||||
This library wraps the OpenAPI-generated client located in the `openapi` directory. The OpenAPI client was generated using the OpenAPI Generator CLI.
|
|
||||||
|
|
||||||
To build the library:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo build
|
|
||||||
```
|
|
||||||
|
|
||||||
To run tests:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cargo test -- --test-threads=1
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
MIT
|
|
@@ -1,42 +0,0 @@
|
|||||||
use sal_rfs_client::types::{ClientConfig, Credentials};
|
|
||||||
use sal_rfs_client::RfsClient;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a client with authentication credentials
|
|
||||||
let config = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: Some(Credentials {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
}),
|
|
||||||
timeout_seconds: 30,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = RfsClient::new(config);
|
|
||||||
println!("Client created with authentication credentials");
|
|
||||||
|
|
||||||
// Authenticate with the server
|
|
||||||
client.authenticate().await?;
|
|
||||||
if client.is_authenticated() {
|
|
||||||
println!("Authentication successful");
|
|
||||||
} else {
|
|
||||||
println!("Authentication failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a client without authentication
|
|
||||||
let config_no_auth = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: None,
|
|
||||||
timeout_seconds: 30,
|
|
||||||
};
|
|
||||||
|
|
||||||
let client_no_auth = RfsClient::new(config_no_auth);
|
|
||||||
println!("Client created without authentication credentials");
|
|
||||||
|
|
||||||
// Check health endpoint (doesn't require authentication)
|
|
||||||
let health = client_no_auth.health_check().await?;
|
|
||||||
println!("Server health: {:?}", health);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@@ -1,153 +0,0 @@
|
|||||||
use openapi::models::{VerifyBlock, VerifyBlocksRequest};
|
|
||||||
use sal_rfs_client::types::{ClientConfig, Credentials};
|
|
||||||
use sal_rfs_client::RfsClient;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a client with authentication
|
|
||||||
let config = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: Some(Credentials {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
}),
|
|
||||||
timeout_seconds: 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = RfsClient::new(config);
|
|
||||||
|
|
||||||
// Authenticate with the server
|
|
||||||
client.authenticate().await?;
|
|
||||||
println!("Authentication successful");
|
|
||||||
|
|
||||||
// Create a test file to upload for block testing
|
|
||||||
let test_file_path = "/tmp/block_test.txt";
|
|
||||||
let test_content = "This is a test file for RFS client block management";
|
|
||||||
std::fs::write(test_file_path, test_content)?;
|
|
||||||
println!("Created test file at {}", test_file_path);
|
|
||||||
|
|
||||||
// Upload the file to get blocks
|
|
||||||
println!("Uploading file to get blocks...");
|
|
||||||
let file_hash = client.upload_file(test_file_path, None).await?;
|
|
||||||
println!("File uploaded with hash: {}", file_hash);
|
|
||||||
|
|
||||||
// Get blocks by file hash
|
|
||||||
println!("Getting blocks for file hash: {}", file_hash);
|
|
||||||
let blocks = client.get_blocks_by_hash(&file_hash).await?;
|
|
||||||
println!("Found {} blocks for the file", blocks.blocks.len());
|
|
||||||
|
|
||||||
// Print block information
|
|
||||||
for (i, block_data) in blocks.blocks.iter().enumerate() {
|
|
||||||
println!(
|
|
||||||
"Block {}: Hash={}, Index={}",
|
|
||||||
i, block_data.hash, block_data.index
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify blocks with complete information
|
|
||||||
println!("Verifying blocks...");
|
|
||||||
|
|
||||||
// Create a list of VerifyBlock objects with complete information
|
|
||||||
let verify_blocks = blocks
|
|
||||||
.blocks
|
|
||||||
.iter()
|
|
||||||
.map(|block| {
|
|
||||||
VerifyBlock {
|
|
||||||
block_hash: block.hash.clone(),
|
|
||||||
block_index: block.index,
|
|
||||||
file_hash: file_hash.clone(), // Using the actual file hash
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Create the request with the complete block information
|
|
||||||
for block in verify_blocks.iter() {
|
|
||||||
println!("Block: {}", block.block_hash);
|
|
||||||
println!("Block index: {}", block.block_index);
|
|
||||||
println!("File hash: {}", block.file_hash);
|
|
||||||
}
|
|
||||||
let request = VerifyBlocksRequest {
|
|
||||||
blocks: verify_blocks,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Send the verification request
|
|
||||||
let verify_result = client.verify_blocks(request).await?;
|
|
||||||
println!(
|
|
||||||
"Verification result: {} missing blocks",
|
|
||||||
verify_result.missing.len()
|
|
||||||
);
|
|
||||||
for block in verify_result.missing.iter() {
|
|
||||||
println!("Missing block: {}", block);
|
|
||||||
}
|
|
||||||
|
|
||||||
// List blocks (list_blocks_handler)
|
|
||||||
println!("\n1. Listing all blocks with pagination...");
|
|
||||||
let blocks_list = client.list_blocks(None).await?;
|
|
||||||
println!("Server has {} blocks in total", blocks_list.len());
|
|
||||||
if !blocks_list.is_empty() {
|
|
||||||
let first_few = blocks_list
|
|
||||||
.iter()
|
|
||||||
.take(3)
|
|
||||||
.map(|s| s.as_str())
|
|
||||||
.collect::<Vec<_>>()
|
|
||||||
.join(", ");
|
|
||||||
println!("First few blocks: {}", first_few);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if a block exists (check_block_handler)
|
|
||||||
if !blocks.blocks.is_empty() {
|
|
||||||
let block_to_check = &blocks.blocks[0].hash;
|
|
||||||
println!("\n2. Checking if block exists: {}", block_to_check);
|
|
||||||
let exists = client.check_block(block_to_check).await?;
|
|
||||||
println!("Block exists: {}", exists);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get block downloads statistics (get_block_downloads_handler)
|
|
||||||
if !blocks.blocks.is_empty() {
|
|
||||||
let block_to_check = &blocks.blocks[0].hash;
|
|
||||||
println!(
|
|
||||||
"\n3. Getting download statistics for block: {}",
|
|
||||||
block_to_check
|
|
||||||
);
|
|
||||||
let downloads = client.get_block_downloads(block_to_check).await?;
|
|
||||||
println!(
|
|
||||||
"Block has been downloaded {} times",
|
|
||||||
downloads.downloads_count
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a specific block content (get_block_handler)
|
|
||||||
if !blocks.blocks.is_empty() {
|
|
||||||
let block_to_get = &blocks.blocks[0].hash;
|
|
||||||
println!("\n4. Getting content for block: {}", block_to_get);
|
|
||||||
let block_content = client.get_block(block_to_get).await?;
|
|
||||||
println!("Retrieved block with {} bytes", block_content.len());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user blocks (get_user_blocks_handler)
|
|
||||||
println!("\n6. Listing user blocks...");
|
|
||||||
let user_blocks = client.get_user_blocks(Some(1), Some(10)).await?;
|
|
||||||
println!(
|
|
||||||
"User has {} blocks (showing page 1 with 10 per page)",
|
|
||||||
user_blocks.total
|
|
||||||
);
|
|
||||||
for block in user_blocks.blocks.iter().take(3) {
|
|
||||||
println!(" - Block: {}, Size: {}", block.hash, block.size);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upload a block (upload_block_handler)
|
|
||||||
println!("\n7. Uploading a new test block...");
|
|
||||||
let test_block_data = b"This is test block data for direct block upload";
|
|
||||||
let new_file_hash = "test_file_hash_for_block_upload";
|
|
||||||
let block_index = 0;
|
|
||||||
let block_hash = client
|
|
||||||
.upload_block(new_file_hash, block_index, test_block_data.to_vec())
|
|
||||||
.await?;
|
|
||||||
println!("Uploaded block with hash: {}", block_hash);
|
|
||||||
|
|
||||||
// Clean up
|
|
||||||
std::fs::remove_file(test_file_path)?;
|
|
||||||
println!("Test file cleaned up");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@@ -1,66 +0,0 @@
|
|||||||
use sal_rfs_client::types::{ClientConfig, Credentials, DownloadOptions, UploadOptions};
|
|
||||||
use sal_rfs_client::RfsClient;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a client with authentication
|
|
||||||
let config = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: Some(Credentials {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
}),
|
|
||||||
timeout_seconds: 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = RfsClient::new(config);
|
|
||||||
|
|
||||||
// Authenticate with the server
|
|
||||||
client.authenticate().await?;
|
|
||||||
println!("Authentication successful");
|
|
||||||
|
|
||||||
// Create a test file to upload
|
|
||||||
let test_file_path = "/tmp/test_upload.txt";
|
|
||||||
std::fs::write(test_file_path, "This is a test file for RFS client upload")?;
|
|
||||||
println!("Created test file at {}", test_file_path);
|
|
||||||
|
|
||||||
// Upload the file with options
|
|
||||||
println!("Uploading file...");
|
|
||||||
let upload_options = UploadOptions {
|
|
||||||
chunk_size: Some(1024 * 1024), // 1MB chunks
|
|
||||||
verify: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let file_hash = client
|
|
||||||
.upload_file(test_file_path, Some(upload_options))
|
|
||||||
.await?;
|
|
||||||
println!("File uploaded with hash: {}", file_hash);
|
|
||||||
|
|
||||||
// Download the file
|
|
||||||
let download_path = "/tmp/test_download.txt";
|
|
||||||
println!("Downloading file to {}...", download_path);
|
|
||||||
|
|
||||||
let download_options = DownloadOptions { verify: true };
|
|
||||||
|
|
||||||
client
|
|
||||||
.download_file(&file_hash, download_path, Some(download_options))
|
|
||||||
.await?;
|
|
||||||
println!("File downloaded to {}", download_path);
|
|
||||||
|
|
||||||
// Verify the downloaded file matches the original
|
|
||||||
let original_content = std::fs::read_to_string(test_file_path)?;
|
|
||||||
let downloaded_content = std::fs::read_to_string(download_path)?;
|
|
||||||
|
|
||||||
if original_content == downloaded_content {
|
|
||||||
println!("File contents match! Download successful.");
|
|
||||||
} else {
|
|
||||||
println!("ERROR: File contents do not match!");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clean up test files
|
|
||||||
std::fs::remove_file(test_file_path)?;
|
|
||||||
std::fs::remove_file(download_path)?;
|
|
||||||
println!("Test files cleaned up");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@@ -1,176 +0,0 @@
|
|||||||
use sal_rfs_client::types::{ClientConfig, Credentials, FlistOptions, WaitOptions};
|
|
||||||
use sal_rfs_client::RfsClient;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
let parent_dir = "flists";
|
|
||||||
// Create a client with authentication
|
|
||||||
let config = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: Some(Credentials {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
}),
|
|
||||||
timeout_seconds: 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = RfsClient::new(config);
|
|
||||||
|
|
||||||
// Authenticate with the server
|
|
||||||
client.authenticate().await?;
|
|
||||||
println!("Authentication successful");
|
|
||||||
|
|
||||||
println!("\n1. CREATE FLIST - Creating an FList from a Docker image");
|
|
||||||
let image_name = "alpine:latest";
|
|
||||||
println!("Creating FList for image: {}", image_name);
|
|
||||||
|
|
||||||
// Use FlistOptions to specify additional parameters
|
|
||||||
let options = FlistOptions {
|
|
||||||
auth: None,
|
|
||||||
username: None,
|
|
||||||
password: None,
|
|
||||||
email: None,
|
|
||||||
server_address: Some("docker.io".to_string()),
|
|
||||||
identity_token: None,
|
|
||||||
registry_token: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create the FList and handle potential conflict error
|
|
||||||
let job_id = match client.create_flist(&image_name, Some(options)).await {
|
|
||||||
Ok(id) => {
|
|
||||||
println!("FList creation started with job ID: {}", id);
|
|
||||||
Some(id)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
if e.to_string().contains("Conflict") {
|
|
||||||
println!("FList already exists");
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
return Err(e.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 2. Check FList state if we have a job ID
|
|
||||||
if let Some(job_id) = &job_id {
|
|
||||||
println!("\n2. GET FLIST STATE - Checking FList creation state");
|
|
||||||
let state = client.get_flist_state(job_id).await?;
|
|
||||||
println!("Current FList state: {:?}", state.flist_state);
|
|
||||||
|
|
||||||
// 3. Wait for FList creation with progress reporting
|
|
||||||
println!("\n3. WAIT FOR FLIST CREATION - Waiting for FList to be created with progress reporting");
|
|
||||||
let wait_options = WaitOptions {
|
|
||||||
timeout_seconds: 60, // Shorter timeout for the example
|
|
||||||
poll_interval_ms: 1000,
|
|
||||||
progress_callback: Some(Box::new(|state| {
|
|
||||||
println!("Progress: FList state is now {:?}", state);
|
|
||||||
// No return value needed (returns unit type)
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wait for the FList to be created (with a timeout)
|
|
||||||
match client
|
|
||||||
.wait_for_flist_creation(job_id, Some(wait_options))
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(final_state) => {
|
|
||||||
println!("FList creation completed with state: {:?}", final_state);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
println!("Error waiting for FList creation: {}", e);
|
|
||||||
// Continue with the example even if waiting fails
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. List all available FLists
|
|
||||||
println!("\n4. LIST FLISTS - Listing all available FLists");
|
|
||||||
|
|
||||||
// Variable to store the FList path for preview and download
|
|
||||||
let mut flist_path_for_preview: Option<String> = None;
|
|
||||||
|
|
||||||
match client.list_flists().await {
|
|
||||||
Ok(flists) => {
|
|
||||||
println!("Found {} FList categories", flists.len());
|
|
||||||
|
|
||||||
for (category, files) in &flists {
|
|
||||||
println!("Category: {}", category);
|
|
||||||
for file in files.iter().take(2) {
|
|
||||||
// Show only first 2 files per category
|
|
||||||
println!(" - {} (size: {} bytes)", file.name, file.size);
|
|
||||||
|
|
||||||
// Save the first FList path for preview
|
|
||||||
if flist_path_for_preview.is_none() {
|
|
||||||
let path = format!("{}/{}/{}", parent_dir, category, file.name);
|
|
||||||
flist_path_for_preview = Some(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if files.len() > 2 {
|
|
||||||
println!(" - ... and {} more files", files.len() - 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. Preview an FList if we found one
|
|
||||||
if let Some(ref flist_path) = flist_path_for_preview {
|
|
||||||
println!("\n5. PREVIEW FLIST - Previewing FList: {}", flist_path);
|
|
||||||
match client.preview_flist(flist_path).await {
|
|
||||||
Ok(preview) => {
|
|
||||||
println!("FList preview for {}:", flist_path);
|
|
||||||
println!(" - Checksum: {}", preview.checksum);
|
|
||||||
println!(" - Metadata: {}", preview.metadata);
|
|
||||||
|
|
||||||
// Display content (list of strings)
|
|
||||||
if !preview.content.is_empty() {
|
|
||||||
println!(" - Content entries:");
|
|
||||||
for (i, entry) in preview.content.iter().enumerate().take(5) {
|
|
||||||
println!(" {}. {}", i + 1, entry);
|
|
||||||
}
|
|
||||||
if preview.content.len() > 5 {
|
|
||||||
println!(" ... and {} more entries", preview.content.len() - 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => println!("Error previewing FList: {}", e),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("No FLists available for preview");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => println!("Error listing FLists: {}", e),
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. DOWNLOAD FLIST - Downloading an FList to a local file
|
|
||||||
if let Some(ref flist_path) = flist_path_for_preview {
|
|
||||||
println!("\n6. DOWNLOAD FLIST - Downloading FList: {}", flist_path);
|
|
||||||
|
|
||||||
// Create a temporary output path for the downloaded FList
|
|
||||||
let output_path = "/tmp/downloaded_flist.fl";
|
|
||||||
|
|
||||||
match client.download_flist(flist_path, output_path).await {
|
|
||||||
Ok(_) => {
|
|
||||||
println!("FList successfully downloaded to {}", output_path);
|
|
||||||
|
|
||||||
// Get file size
|
|
||||||
match std::fs::metadata(output_path) {
|
|
||||||
Ok(metadata) => println!("Downloaded file size: {} bytes", metadata.len()),
|
|
||||||
Err(e) => println!("Error getting file metadata: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => println!("Error downloading FList: {}", e),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("\n6. DOWNLOAD FLIST - No FList available for download");
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("\nAll FList operations demonstrated:");
|
|
||||||
println!("1. create_flist - Create a new FList from a Docker image");
|
|
||||||
println!("2. get_flist_state - Check the state of an FList creation job");
|
|
||||||
println!(
|
|
||||||
"3. wait_for_flist_creation - Wait for an FList to be created with progress reporting"
|
|
||||||
);
|
|
||||||
println!("4. list_flists - List all available FLists");
|
|
||||||
println!("5. preview_flist - Preview the content of an FList");
|
|
||||||
println!("6. download_flist - Download an FList to a local file");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
@@ -1,64 +0,0 @@
|
|||||||
use openapi::models::FlistState;
|
|
||||||
use sal_rfs_client::types::{ClientConfig, Credentials, WaitOptions};
|
|
||||||
use sal_rfs_client::RfsClient;
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
||||||
// Create a client with authentication
|
|
||||||
let config = ClientConfig {
|
|
||||||
base_url: "http://localhost:8080".to_string(),
|
|
||||||
credentials: Some(Credentials {
|
|
||||||
username: "user".to_string(),
|
|
||||||
password: "password".to_string(),
|
|
||||||
}),
|
|
||||||
timeout_seconds: 60,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = RfsClient::new(config);
|
|
||||||
|
|
||||||
// Authenticate with the server
|
|
||||||
client.authenticate().await?;
|
|
||||||
println!("Authentication successful");
|
|
||||||
|
|
||||||
// Create an FList from a Docker image
|
|
||||||
let image_name = "redis:latest";
|
|
||||||
println!("Creating FList for image: {}", image_name);
|
|
||||||
|
|
||||||
let job_id = client.create_flist(&image_name, None).await?;
|
|
||||||
println!("FList creation started with job ID: {}", job_id);
|
|
||||||
|
|
||||||
// Set up options for waiting with progress reporting
|
|
||||||
let options = WaitOptions {
|
|
||||||
timeout_seconds: 600, // 10 minutes timeout
|
|
||||||
poll_interval_ms: 2000, // Check every 2 seconds
|
|
||||||
progress_callback: Some(Box::new(|state| match state {
|
|
||||||
FlistState::FlistStateInProgress(info) => {
|
|
||||||
println!(
|
|
||||||
"Progress: {:.1}% - {}",
|
|
||||||
info.in_progress.progress, info.in_progress.msg
|
|
||||||
);
|
|
||||||
}
|
|
||||||
FlistState::FlistStateStarted(_) => {
|
|
||||||
println!("FList creation started...");
|
|
||||||
}
|
|
||||||
FlistState::FlistStateAccepted(_) => {
|
|
||||||
println!("FList creation request accepted...");
|
|
||||||
}
|
|
||||||
_ => println!("State: {:?}", state),
|
|
||||||
})),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Wait for the FList to be created
|
|
||||||
println!("Waiting for FList creation to complete...");
|
|
||||||
|
|
||||||
// Use ? operator to propagate errors properly
|
|
||||||
let state = client
|
|
||||||
.wait_for_flist_creation(&job_id, Some(options))
|
|
||||||
.await
|
|
||||||
.map_err(|e| -> Box<dyn std::error::Error> { Box::new(e) })?;
|
|
||||||
|
|
||||||
println!("FList created successfully!");
|
|
||||||
println!("Final state: {:?}", state);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
@@ -1,3 +0,0 @@
|
|||||||
/target/
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
@@ -1,23 +0,0 @@
|
|||||||
# OpenAPI Generator Ignore
|
|
||||||
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
|
|
||||||
|
|
||||||
# Use this file to prevent files from being overwritten by the generator.
|
|
||||||
# The patterns follow closely to .gitignore or .dockerignore.
|
|
||||||
|
|
||||||
# As an example, the C# client generator defines ApiClient.cs.
|
|
||||||
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
|
|
||||||
#ApiClient.cs
|
|
||||||
|
|
||||||
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
|
|
||||||
#foo/*/qux
|
|
||||||
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
|
|
||||||
|
|
||||||
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
|
|
||||||
#foo/**/qux
|
|
||||||
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
|
|
||||||
|
|
||||||
# You can also negate patterns with an exclamation (!).
|
|
||||||
# For example, you can ignore all files in a docs folder with the file extension .md:
|
|
||||||
#docs/*.md
|
|
||||||
# Then explicitly reverse the ignore rule for a single file:
|
|
||||||
#!docs/README.md
|
|
@@ -1,125 +0,0 @@
|
|||||||
.gitignore
|
|
||||||
.travis.yml
|
|
||||||
Cargo.toml
|
|
||||||
README.md
|
|
||||||
docs/AuthenticationApi.md
|
|
||||||
docs/BlockDownloadsResponse.md
|
|
||||||
docs/BlockInfo.md
|
|
||||||
docs/BlockManagementApi.md
|
|
||||||
docs/BlockUploadedResponse.md
|
|
||||||
docs/BlocksResponse.md
|
|
||||||
docs/DirListTemplate.md
|
|
||||||
docs/DirLister.md
|
|
||||||
docs/ErrorTemplate.md
|
|
||||||
docs/FileDownloadRequest.md
|
|
||||||
docs/FileInfo.md
|
|
||||||
docs/FileManagementApi.md
|
|
||||||
docs/FileUploadResponse.md
|
|
||||||
docs/FlistBody.md
|
|
||||||
docs/FlistManagementApi.md
|
|
||||||
docs/FlistState.md
|
|
||||||
docs/FlistStateAccepted.md
|
|
||||||
docs/FlistStateCreated.md
|
|
||||||
docs/FlistStateInProgress.md
|
|
||||||
docs/FlistStateInfo.md
|
|
||||||
docs/FlistStateResponse.md
|
|
||||||
docs/FlistStateStarted.md
|
|
||||||
docs/HealthResponse.md
|
|
||||||
docs/Job.md
|
|
||||||
docs/ListBlocksParams.md
|
|
||||||
docs/ListBlocksResponse.md
|
|
||||||
docs/PreviewResponse.md
|
|
||||||
docs/ResponseError.md
|
|
||||||
docs/ResponseErrorBadRequest.md
|
|
||||||
docs/ResponseErrorConflict.md
|
|
||||||
docs/ResponseErrorForbidden.md
|
|
||||||
docs/ResponseErrorNotFound.md
|
|
||||||
docs/ResponseErrorTemplateError.md
|
|
||||||
docs/ResponseErrorUnauthorized.md
|
|
||||||
docs/ResponseResult.md
|
|
||||||
docs/ResponseResultBlockUploaded.md
|
|
||||||
docs/ResponseResultDirTemplate.md
|
|
||||||
docs/ResponseResultFileUploaded.md
|
|
||||||
docs/ResponseResultFlistCreated.md
|
|
||||||
docs/ResponseResultFlistState.md
|
|
||||||
docs/ResponseResultFlists.md
|
|
||||||
docs/ResponseResultPreviewFlist.md
|
|
||||||
docs/ResponseResultRes.md
|
|
||||||
docs/ResponseResultSignedIn.md
|
|
||||||
docs/SignInBody.md
|
|
||||||
docs/SignInResponse.md
|
|
||||||
docs/SystemApi.md
|
|
||||||
docs/TemplateErr.md
|
|
||||||
docs/TemplateErrBadRequest.md
|
|
||||||
docs/TemplateErrInternalServerError.md
|
|
||||||
docs/TemplateErrNotFound.md
|
|
||||||
docs/UploadBlockParams.md
|
|
||||||
docs/UserBlockInfo.md
|
|
||||||
docs/UserBlocksResponse.md
|
|
||||||
docs/VerifyBlock.md
|
|
||||||
docs/VerifyBlocksRequest.md
|
|
||||||
docs/VerifyBlocksResponse.md
|
|
||||||
docs/WebsiteServingApi.md
|
|
||||||
git_push.sh
|
|
||||||
src/apis/authentication_api.rs
|
|
||||||
src/apis/block_management_api.rs
|
|
||||||
src/apis/configuration.rs
|
|
||||||
src/apis/file_management_api.rs
|
|
||||||
src/apis/flist_management_api.rs
|
|
||||||
src/apis/mod.rs
|
|
||||||
src/apis/system_api.rs
|
|
||||||
src/apis/website_serving_api.rs
|
|
||||||
src/lib.rs
|
|
||||||
src/models/block_downloads_response.rs
|
|
||||||
src/models/block_info.rs
|
|
||||||
src/models/block_uploaded_response.rs
|
|
||||||
src/models/blocks_response.rs
|
|
||||||
src/models/dir_list_template.rs
|
|
||||||
src/models/dir_lister.rs
|
|
||||||
src/models/error_template.rs
|
|
||||||
src/models/file_download_request.rs
|
|
||||||
src/models/file_info.rs
|
|
||||||
src/models/file_upload_response.rs
|
|
||||||
src/models/flist_body.rs
|
|
||||||
src/models/flist_state.rs
|
|
||||||
src/models/flist_state_accepted.rs
|
|
||||||
src/models/flist_state_created.rs
|
|
||||||
src/models/flist_state_in_progress.rs
|
|
||||||
src/models/flist_state_info.rs
|
|
||||||
src/models/flist_state_response.rs
|
|
||||||
src/models/flist_state_started.rs
|
|
||||||
src/models/health_response.rs
|
|
||||||
src/models/job.rs
|
|
||||||
src/models/list_blocks_params.rs
|
|
||||||
src/models/list_blocks_response.rs
|
|
||||||
src/models/mod.rs
|
|
||||||
src/models/preview_response.rs
|
|
||||||
src/models/response_error.rs
|
|
||||||
src/models/response_error_bad_request.rs
|
|
||||||
src/models/response_error_conflict.rs
|
|
||||||
src/models/response_error_forbidden.rs
|
|
||||||
src/models/response_error_not_found.rs
|
|
||||||
src/models/response_error_template_error.rs
|
|
||||||
src/models/response_error_unauthorized.rs
|
|
||||||
src/models/response_result.rs
|
|
||||||
src/models/response_result_block_uploaded.rs
|
|
||||||
src/models/response_result_dir_template.rs
|
|
||||||
src/models/response_result_file_uploaded.rs
|
|
||||||
src/models/response_result_flist_created.rs
|
|
||||||
src/models/response_result_flist_state.rs
|
|
||||||
src/models/response_result_flists.rs
|
|
||||||
src/models/response_result_preview_flist.rs
|
|
||||||
src/models/response_result_res.rs
|
|
||||||
src/models/response_result_signed_in.rs
|
|
||||||
src/models/sign_in_body.rs
|
|
||||||
src/models/sign_in_response.rs
|
|
||||||
src/models/template_err.rs
|
|
||||||
src/models/template_err_bad_request.rs
|
|
||||||
src/models/template_err_internal_server_error.rs
|
|
||||||
src/models/template_err_not_found.rs
|
|
||||||
src/models/upload_block_params.rs
|
|
||||||
src/models/user_block_info.rs
|
|
||||||
src/models/user_blocks_response.rs
|
|
||||||
src/models/verify_block.rs
|
|
||||||
src/models/verify_blocks_request.rs
|
|
||||||
src/models/verify_blocks_response.rs
|
|
@@ -1 +0,0 @@
|
|||||||
7.13.0
|
|
@@ -1 +0,0 @@
|
|||||||
language: rust
|
|
@@ -1,15 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "openapi"
|
|
||||||
version = "0.2.0"
|
|
||||||
authors = ["OpenAPI Generator team and contributors"]
|
|
||||||
description = "No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)"
|
|
||||||
license = ""
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
serde = { version = "^1.0", features = ["derive"] }
|
|
||||||
serde_with = { version = "^3.8", default-features = false, features = ["base64", "std", "macros"] }
|
|
||||||
serde_json = "^1.0"
|
|
||||||
serde_repr = "^0.1"
|
|
||||||
url = "^2.5"
|
|
||||||
reqwest = { version = "^0.12", default-features = false, features = ["json", "multipart"] }
|
|
@@ -1,114 +0,0 @@
|
|||||||
# Rust API client for openapi
|
|
||||||
|
|
||||||
No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
|
||||||
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This API client was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the [openapi-spec](https://openapis.org) from a remote server, you can easily generate an API client.
|
|
||||||
|
|
||||||
- API version: 0.2.0
|
|
||||||
- Package version: 0.2.0
|
|
||||||
- Generator version: 7.13.0
|
|
||||||
- Build package: `org.openapitools.codegen.languages.RustClientCodegen`
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Put the package under your project folder in a directory named `openapi` and add the following to `Cargo.toml` under `[dependencies]`:
|
|
||||||
|
|
||||||
```
|
|
||||||
openapi = { path = "./openapi" }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Documentation for API Endpoints
|
|
||||||
|
|
||||||
All URIs are relative to *http://localhost*
|
|
||||||
|
|
||||||
Class | Method | HTTP request | Description
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
*AuthenticationApi* | [**sign_in_handler**](docs/AuthenticationApi.md#sign_in_handler) | **POST** /api/v1/signin |
|
|
||||||
*BlockManagementApi* | [**check_block_handler**](docs/BlockManagementApi.md#check_block_handler) | **HEAD** /api/v1/block/{hash} | Checks a block by its hash.
|
|
||||||
*BlockManagementApi* | [**get_block_downloads_handler**](docs/BlockManagementApi.md#get_block_downloads_handler) | **GET** /api/v1/block/{hash}/downloads | Retrieve the number of times a block has been downloaded.
|
|
||||||
*BlockManagementApi* | [**get_block_handler**](docs/BlockManagementApi.md#get_block_handler) | **GET** /api/v1/block/{hash} | Retrieve a block by its hash.
|
|
||||||
*BlockManagementApi* | [**get_blocks_by_hash_handler**](docs/BlockManagementApi.md#get_blocks_by_hash_handler) | **GET** /api/v1/blocks/{hash} | Retrieve blocks by hash (file hash or block hash).
|
|
||||||
*BlockManagementApi* | [**get_user_blocks_handler**](docs/BlockManagementApi.md#get_user_blocks_handler) | **GET** /api/v1/user/blocks | Retrieve all blocks uploaded by a specific user.
|
|
||||||
*BlockManagementApi* | [**list_blocks_handler**](docs/BlockManagementApi.md#list_blocks_handler) | **GET** /api/v1/blocks | List all block hashes in the server with pagination
|
|
||||||
*BlockManagementApi* | [**upload_block_handler**](docs/BlockManagementApi.md#upload_block_handler) | **POST** /api/v1/block | Upload a block to the server.
|
|
||||||
*BlockManagementApi* | [**verify_blocks_handler**](docs/BlockManagementApi.md#verify_blocks_handler) | **POST** /api/v1/block/verify | Verify if multiple blocks exist on the server.
|
|
||||||
*FileManagementApi* | [**get_file_handler**](docs/FileManagementApi.md#get_file_handler) | **GET** /api/v1/file/{hash} | Retrieve a file by its hash from path, with optional custom filename in request body.
|
|
||||||
*FileManagementApi* | [**upload_file_handler**](docs/FileManagementApi.md#upload_file_handler) | **POST** /api/v1/file | Upload a file to the server.
|
|
||||||
*FlistManagementApi* | [**create_flist_handler**](docs/FlistManagementApi.md#create_flist_handler) | **POST** /api/v1/fl |
|
|
||||||
*FlistManagementApi* | [**get_flist_state_handler**](docs/FlistManagementApi.md#get_flist_state_handler) | **GET** /api/v1/fl/{job_id} |
|
|
||||||
*FlistManagementApi* | [**list_flists_handler**](docs/FlistManagementApi.md#list_flists_handler) | **GET** /api/v1/fl |
|
|
||||||
*FlistManagementApi* | [**preview_flist_handler**](docs/FlistManagementApi.md#preview_flist_handler) | **GET** /api/v1/fl/preview/{flist_path} |
|
|
||||||
*FlistManagementApi* | [**serve_flists**](docs/FlistManagementApi.md#serve_flists) | **GET** /{path} | Serve flist files from the server's filesystem
|
|
||||||
*SystemApi* | [**health_check_handler**](docs/SystemApi.md#health_check_handler) | **GET** /api/v1 |
|
|
||||||
*WebsiteServingApi* | [**serve_website_handler**](docs/WebsiteServingApi.md#serve_website_handler) | **GET** /api/v1/website/{website_hash}/{path} |
|
|
||||||
|
|
||||||
|
|
||||||
## Documentation For Models
|
|
||||||
|
|
||||||
- [BlockDownloadsResponse](docs/BlockDownloadsResponse.md)
|
|
||||||
- [BlockInfo](docs/BlockInfo.md)
|
|
||||||
- [BlockUploadedResponse](docs/BlockUploadedResponse.md)
|
|
||||||
- [BlocksResponse](docs/BlocksResponse.md)
|
|
||||||
- [DirListTemplate](docs/DirListTemplate.md)
|
|
||||||
- [DirLister](docs/DirLister.md)
|
|
||||||
- [ErrorTemplate](docs/ErrorTemplate.md)
|
|
||||||
- [FileDownloadRequest](docs/FileDownloadRequest.md)
|
|
||||||
- [FileInfo](docs/FileInfo.md)
|
|
||||||
- [FileUploadResponse](docs/FileUploadResponse.md)
|
|
||||||
- [FlistBody](docs/FlistBody.md)
|
|
||||||
- [FlistState](docs/FlistState.md)
|
|
||||||
- [FlistStateAccepted](docs/FlistStateAccepted.md)
|
|
||||||
- [FlistStateCreated](docs/FlistStateCreated.md)
|
|
||||||
- [FlistStateInProgress](docs/FlistStateInProgress.md)
|
|
||||||
- [FlistStateInfo](docs/FlistStateInfo.md)
|
|
||||||
- [FlistStateResponse](docs/FlistStateResponse.md)
|
|
||||||
- [FlistStateStarted](docs/FlistStateStarted.md)
|
|
||||||
- [HealthResponse](docs/HealthResponse.md)
|
|
||||||
- [Job](docs/Job.md)
|
|
||||||
- [ListBlocksParams](docs/ListBlocksParams.md)
|
|
||||||
- [ListBlocksResponse](docs/ListBlocksResponse.md)
|
|
||||||
- [PreviewResponse](docs/PreviewResponse.md)
|
|
||||||
- [ResponseError](docs/ResponseError.md)
|
|
||||||
- [ResponseErrorBadRequest](docs/ResponseErrorBadRequest.md)
|
|
||||||
- [ResponseErrorConflict](docs/ResponseErrorConflict.md)
|
|
||||||
- [ResponseErrorForbidden](docs/ResponseErrorForbidden.md)
|
|
||||||
- [ResponseErrorNotFound](docs/ResponseErrorNotFound.md)
|
|
||||||
- [ResponseErrorTemplateError](docs/ResponseErrorTemplateError.md)
|
|
||||||
- [ResponseErrorUnauthorized](docs/ResponseErrorUnauthorized.md)
|
|
||||||
- [ResponseResult](docs/ResponseResult.md)
|
|
||||||
- [ResponseResultBlockUploaded](docs/ResponseResultBlockUploaded.md)
|
|
||||||
- [ResponseResultDirTemplate](docs/ResponseResultDirTemplate.md)
|
|
||||||
- [ResponseResultFileUploaded](docs/ResponseResultFileUploaded.md)
|
|
||||||
- [ResponseResultFlistCreated](docs/ResponseResultFlistCreated.md)
|
|
||||||
- [ResponseResultFlistState](docs/ResponseResultFlistState.md)
|
|
||||||
- [ResponseResultFlists](docs/ResponseResultFlists.md)
|
|
||||||
- [ResponseResultPreviewFlist](docs/ResponseResultPreviewFlist.md)
|
|
||||||
- [ResponseResultRes](docs/ResponseResultRes.md)
|
|
||||||
- [ResponseResultSignedIn](docs/ResponseResultSignedIn.md)
|
|
||||||
- [SignInBody](docs/SignInBody.md)
|
|
||||||
- [SignInResponse](docs/SignInResponse.md)
|
|
||||||
- [TemplateErr](docs/TemplateErr.md)
|
|
||||||
- [TemplateErrBadRequest](docs/TemplateErrBadRequest.md)
|
|
||||||
- [TemplateErrInternalServerError](docs/TemplateErrInternalServerError.md)
|
|
||||||
- [TemplateErrNotFound](docs/TemplateErrNotFound.md)
|
|
||||||
- [UploadBlockParams](docs/UploadBlockParams.md)
|
|
||||||
- [UserBlockInfo](docs/UserBlockInfo.md)
|
|
||||||
- [UserBlocksResponse](docs/UserBlocksResponse.md)
|
|
||||||
- [VerifyBlock](docs/VerifyBlock.md)
|
|
||||||
- [VerifyBlocksRequest](docs/VerifyBlocksRequest.md)
|
|
||||||
- [VerifyBlocksResponse](docs/VerifyBlocksResponse.md)
|
|
||||||
|
|
||||||
|
|
||||||
To get access to the crate's generated documentation, use:
|
|
||||||
|
|
||||||
```
|
|
||||||
cargo doc --open
|
|
||||||
```
|
|
||||||
|
|
||||||
## Author
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@@ -1,37 +0,0 @@
|
|||||||
# \AuthenticationApi
|
|
||||||
|
|
||||||
All URIs are relative to *http://localhost*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**sign_in_handler**](AuthenticationApi.md#sign_in_handler) | **POST** /api/v1/signin |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## sign_in_handler
|
|
||||||
|
|
||||||
> models::SignInResponse sign_in_handler(sign_in_body)
|
|
||||||
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**sign_in_body** | [**SignInBody**](SignInBody.md) | | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::SignInResponse**](SignInResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
@@ -1,14 +0,0 @@
|
|||||||
# Block
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**data** | [**std::path::PathBuf**](std::path::PathBuf.md) | |
|
|
||||||
**hash** | **String** | |
|
|
||||||
**index** | **i64** | |
|
|
||||||
**size** | **i32** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,13 +0,0 @@
|
|||||||
# BlockDownloadsResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**block_hash** | **String** | Block hash |
|
|
||||||
**block_size** | **i64** | Size of the block in bytes |
|
|
||||||
**downloads_count** | **i64** | Number of times the block has been downloaded |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# BlockInfo
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | Block hash |
|
|
||||||
**index** | **i64** | Block index within the file |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,250 +0,0 @@
|
|||||||
# \BlockManagementApi
|
|
||||||
|
|
||||||
All URIs are relative to *http://localhost*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**check_block_handler**](BlockManagementApi.md#check_block_handler) | **HEAD** /api/v1/block/{hash} | Checks a block by its hash.
|
|
||||||
[**get_block_downloads_handler**](BlockManagementApi.md#get_block_downloads_handler) | **GET** /api/v1/block/{hash}/downloads | Retrieve the number of times a block has been downloaded.
|
|
||||||
[**get_block_handler**](BlockManagementApi.md#get_block_handler) | **GET** /api/v1/block/{hash} | Retrieve a block by its hash.
|
|
||||||
[**get_blocks_by_hash_handler**](BlockManagementApi.md#get_blocks_by_hash_handler) | **GET** /api/v1/blocks/{hash} | Retrieve blocks by hash (file hash or block hash).
|
|
||||||
[**get_user_blocks_handler**](BlockManagementApi.md#get_user_blocks_handler) | **GET** /api/v1/user/blocks | Retrieve all blocks uploaded by a specific user.
|
|
||||||
[**list_blocks_handler**](BlockManagementApi.md#list_blocks_handler) | **GET** /api/v1/blocks | List all block hashes in the server with pagination
|
|
||||||
[**upload_block_handler**](BlockManagementApi.md#upload_block_handler) | **POST** /api/v1/block | Upload a block to the server.
|
|
||||||
[**verify_blocks_handler**](BlockManagementApi.md#verify_blocks_handler) | **POST** /api/v1/block/verify | Verify if multiple blocks exist on the server.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## check_block_handler
|
|
||||||
|
|
||||||
> check_block_handler(hash)
|
|
||||||
Checks a block by its hash.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | Block hash | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
(empty response body)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_block_downloads_handler
|
|
||||||
|
|
||||||
> models::BlockDownloadsResponse get_block_downloads_handler(hash)
|
|
||||||
Retrieve the number of times a block has been downloaded.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | Block hash | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::BlockDownloadsResponse**](BlockDownloadsResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_block_handler
|
|
||||||
|
|
||||||
> std::path::PathBuf get_block_handler(hash)
|
|
||||||
Retrieve a block by its hash.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | Block hash | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**std::path::PathBuf**](std::path::PathBuf.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/octet-stream, application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_blocks_by_hash_handler
|
|
||||||
|
|
||||||
> models::BlocksResponse get_blocks_by_hash_handler(hash)
|
|
||||||
Retrieve blocks by hash (file hash or block hash).
|
|
||||||
|
|
||||||
If the hash is a file hash, returns all blocks with their block index related to that file. If the hash is a block hash, returns the block itself.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | File hash or block hash | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::BlocksResponse**](BlocksResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_user_blocks_handler
|
|
||||||
|
|
||||||
> models::UserBlocksResponse get_user_blocks_handler(page, per_page)
|
|
||||||
Retrieve all blocks uploaded by a specific user.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**page** | Option<**i32**> | Page number (1-indexed) | |
|
|
||||||
**per_page** | Option<**i32**> | Number of items per page | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::UserBlocksResponse**](UserBlocksResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[bearerAuth](../README.md#bearerAuth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_blocks_handler
|
|
||||||
|
|
||||||
> models::ListBlocksResponse list_blocks_handler(page, per_page)
|
|
||||||
List all block hashes in the server with pagination
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**page** | Option<**i32**> | Page number (1-indexed) | |
|
|
||||||
**per_page** | Option<**i32**> | Number of items per page | |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::ListBlocksResponse**](ListBlocksResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## upload_block_handler
|
|
||||||
|
|
||||||
> models::BlockUploadedResponse upload_block_handler(file_hash, idx, body)
|
|
||||||
Upload a block to the server.
|
|
||||||
|
|
||||||
If the block already exists, the server will return a 200 OK response. If the block is new, the server will return a 201 Created response.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**file_hash** | **String** | File hash associated with the block | [required] |
|
|
||||||
**idx** | **i64** | Block index within the file | [required] |
|
|
||||||
**body** | **std::path::PathBuf** | Block data to upload | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::BlockUploadedResponse**](BlockUploadedResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[bearerAuth](../README.md#bearerAuth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/octet-stream
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## verify_blocks_handler
|
|
||||||
|
|
||||||
> models::VerifyBlocksResponse verify_blocks_handler(verify_blocks_request)
|
|
||||||
Verify if multiple blocks exist on the server.
|
|
||||||
|
|
||||||
Returns a list of missing blocks.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**verify_blocks_request** | [**VerifyBlocksRequest**](VerifyBlocksRequest.md) | List of block hashes to verify | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::VerifyBlocksResponse**](VerifyBlocksResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# BlockUploadedResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | |
|
|
||||||
**message** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# BlocksResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**blocks** | [**Vec<models::BlockInfo>**](BlockInfo.md) | List of blocks with their indices |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# DirListTemplate
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**cur_path** | **String** | |
|
|
||||||
**lister** | [**models::DirLister**](DirLister.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# DirLister
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**files** | [**Vec<models::FileInfo>**](FileInfo.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,13 +0,0 @@
|
|||||||
# ErrorTemplate
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**cur_path** | **String** | |
|
|
||||||
**err** | [**models::TemplateErr**](TemplateErr.md) | |
|
|
||||||
**message** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# File
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**file_content** | [**std::path::PathBuf**](std::path::PathBuf.md) | |
|
|
||||||
**file_hash** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# FileDownloadRequest
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**file_name** | **String** | The custom filename to use for download |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,16 +0,0 @@
|
|||||||
# FileInfo
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**is_file** | **bool** | |
|
|
||||||
**last_modified** | **i64** | |
|
|
||||||
**name** | **String** | |
|
|
||||||
**path_uri** | **String** | |
|
|
||||||
**progress** | **f32** | |
|
|
||||||
**size** | **i64** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,71 +0,0 @@
|
|||||||
# \FileManagementApi
|
|
||||||
|
|
||||||
All URIs are relative to *http://localhost*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**get_file_handler**](FileManagementApi.md#get_file_handler) | **GET** /api/v1/file/{hash} | Retrieve a file by its hash from path, with optional custom filename in request body.
|
|
||||||
[**upload_file_handler**](FileManagementApi.md#upload_file_handler) | **POST** /api/v1/file | Upload a file to the server.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## get_file_handler
|
|
||||||
|
|
||||||
> std::path::PathBuf get_file_handler(hash, file_download_request)
|
|
||||||
Retrieve a file by its hash from path, with optional custom filename in request body.
|
|
||||||
|
|
||||||
The file will be reconstructed from its blocks.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | File hash | [required] |
|
|
||||||
**file_download_request** | [**FileDownloadRequest**](FileDownloadRequest.md) | Optional custom filename for download | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**std::path::PathBuf**](std::path::PathBuf.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/octet-stream, application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## upload_file_handler
|
|
||||||
|
|
||||||
> models::FileUploadResponse upload_file_handler(body)
|
|
||||||
Upload a file to the server.
|
|
||||||
|
|
||||||
The file will be split into blocks and stored in the database.
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**body** | **std::path::PathBuf** | File data to upload | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::FileUploadResponse**](FileUploadResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[bearerAuth](../README.md#bearerAuth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/octet-stream
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# FileUploadResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**file_hash** | **String** | The file hash |
|
|
||||||
**message** | **String** | Message indicating success |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,18 +0,0 @@
|
|||||||
# FlistBody
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**auth** | Option<**String**> | | [optional]
|
|
||||||
**email** | Option<**String**> | | [optional]
|
|
||||||
**identity_token** | Option<**String**> | | [optional]
|
|
||||||
**image_name** | **String** | |
|
|
||||||
**password** | Option<**String**> | | [optional]
|
|
||||||
**registry_token** | Option<**String**> | | [optional]
|
|
||||||
**server_address** | Option<**String**> | | [optional]
|
|
||||||
**username** | Option<**String**> | | [optional]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,150 +0,0 @@
|
|||||||
# \FlistManagementApi
|
|
||||||
|
|
||||||
All URIs are relative to *http://localhost*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**create_flist_handler**](FlistManagementApi.md#create_flist_handler) | **POST** /api/v1/fl |
|
|
||||||
[**get_flist_state_handler**](FlistManagementApi.md#get_flist_state_handler) | **GET** /api/v1/fl/{job_id} |
|
|
||||||
[**list_flists_handler**](FlistManagementApi.md#list_flists_handler) | **GET** /api/v1/fl |
|
|
||||||
[**preview_flist_handler**](FlistManagementApi.md#preview_flist_handler) | **GET** /api/v1/fl/preview/{flist_path} |
|
|
||||||
[**serve_flists**](FlistManagementApi.md#serve_flists) | **GET** /{path} | Serve flist files from the server's filesystem
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## create_flist_handler
|
|
||||||
|
|
||||||
> models::Job create_flist_handler(flist_body)
|
|
||||||
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**flist_body** | [**FlistBody**](FlistBody.md) | | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::Job**](Job.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[bearerAuth](../README.md#bearerAuth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: application/json
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## get_flist_state_handler
|
|
||||||
|
|
||||||
> models::FlistStateResponse get_flist_state_handler(job_id)
|
|
||||||
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**job_id** | **String** | flist job id | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::FlistStateResponse**](FlistStateResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
[bearerAuth](../README.md#bearerAuth)
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## list_flists_handler
|
|
||||||
|
|
||||||
> std::collections::HashMap<String, Vec<models::FileInfo>> list_flists_handler()
|
|
||||||
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
This endpoint does not need any parameter.
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**std::collections::HashMap<String, Vec<models::FileInfo>>**](Vec.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## preview_flist_handler
|
|
||||||
|
|
||||||
> models::PreviewResponse preview_flist_handler(flist_path)
|
|
||||||
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**flist_path** | **String** | flist file path | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::PreviewResponse**](PreviewResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
||||||
## serve_flists
|
|
||||||
|
|
||||||
> std::path::PathBuf serve_flists(path)
|
|
||||||
Serve flist files from the server's filesystem
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**path** | **String** | Path to the flist file or directory to serve | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**std::path::PathBuf**](std::path::PathBuf.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/octet-stream, application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
@@ -1,37 +0,0 @@
|
|||||||
# \FlistServingApi
|
|
||||||
|
|
||||||
All URIs are relative to *http://localhost*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**serve_flists**](FlistServingApi.md#serve_flists) | **GET** /{path} | Serve flist files from the server's filesystem
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## serve_flists
|
|
||||||
|
|
||||||
> models::ResponseResult serve_flists(path)
|
|
||||||
Serve flist files from the server's filesystem
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
|
|
||||||
Name | Type | Description | Required | Notes
|
|
||||||
------------- | ------------- | ------------- | ------------- | -------------
|
|
||||||
**path** | **String** | Path to the flist file or directory to serve | [required] |
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::ResponseResult**](ResponseResult.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
@@ -1,15 +0,0 @@
|
|||||||
# FlistState
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Description |
|
|
||||||
|---- | -----|
|
|
||||||
| FlistStateAccepted | |
|
|
||||||
| FlistStateCreated | |
|
|
||||||
| FlistStateInProgress | |
|
|
||||||
| FlistStateStarted | |
|
|
||||||
| String | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# FlistStateAccepted
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**accepted** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# FlistStateCreated
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**created** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# FlistStateInProgress
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**in_progress** | [**models::FlistStateInfo**](FlistStateInfo.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# FlistStateInfo
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**msg** | **String** | |
|
|
||||||
**progress** | **f32** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# FlistStateResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**flist_state** | [**models::FlistState**](FlistState.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# FlistStateStarted
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**started** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# HealthResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**msg** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# Job
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**id** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# ListBlocksParams
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**page** | Option<**i32**> | Page number (1-indexed) | [optional][default to 1]
|
|
||||||
**per_page** | Option<**i32**> | Number of items per page | [optional][default to 50]
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,14 +0,0 @@
|
|||||||
# ListBlocksResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**blocks** | **Vec<String>** | List of block hashes |
|
|
||||||
**page** | **i32** | Current page number |
|
|
||||||
**per_page** | **i32** | Number of items per page |
|
|
||||||
**total** | **i64** | Total number of blocks |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,13 +0,0 @@
|
|||||||
# PreviewResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**checksum** | **String** | |
|
|
||||||
**content** | **Vec<String>** | |
|
|
||||||
**metadata** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,17 +0,0 @@
|
|||||||
# ResponseError
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Description |
|
|
||||||
|---- | -----|
|
|
||||||
| ResponseErrorBadRequest | |
|
|
||||||
| ResponseErrorConflict | |
|
|
||||||
| ResponseErrorForbidden | |
|
|
||||||
| ResponseErrorNotFound | |
|
|
||||||
| ResponseErrorTemplateError | |
|
|
||||||
| ResponseErrorUnauthorized | |
|
|
||||||
| String | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseErrorBadRequest
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**bad_request** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseErrorConflict
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**conflict** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseErrorForbidden
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**forbidden** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseErrorNotFound
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**not_found** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseErrorTemplateError
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**template_error** | [**models::ErrorTemplate**](ErrorTemplate.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseErrorUnauthorized
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**unauthorized** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,20 +0,0 @@
|
|||||||
# ResponseResult
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Description |
|
|
||||||
|---- | -----|
|
|
||||||
| ResponseResultBlockUploaded | |
|
|
||||||
| ResponseResultDirTemplate | |
|
|
||||||
| ResponseResultFileUploaded | |
|
|
||||||
| ResponseResultFlistCreated | |
|
|
||||||
| ResponseResultFlistState | |
|
|
||||||
| ResponseResultFlists | |
|
|
||||||
| ResponseResultPreviewFlist | |
|
|
||||||
| ResponseResultRes | |
|
|
||||||
| ResponseResultSignedIn | |
|
|
||||||
| String | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultBlockUploaded
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**block_uploaded** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultDirTemplate
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**dir_template** | [**models::DirListTemplate**](DirListTemplate.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultFileUploaded
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**file_uploaded** | [**models::FileUploadResponse**](FileUploadResponse.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultFlistCreated
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**flist_created** | [**models::Job**](Job.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultFlistState
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**flist_state** | [**models::FlistState**](FlistState.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultFlists
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**flists** | [**std::collections::HashMap<String, Vec<models::FileInfo>>**](Vec.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultPreviewFlist
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**preview_flist** | [**models::PreviewResponse**](PreviewResponse.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultRes
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**res** | [**std::path::PathBuf**](std::path::PathBuf.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# ResponseResultSignedIn
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**signed_in** | [**models::SignInResponse**](SignInResponse.md) | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# SignInBody
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**password** | **String** | |
|
|
||||||
**username** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# SignInResponse
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**access_token** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,34 +0,0 @@
|
|||||||
# \SystemApi
|
|
||||||
|
|
||||||
All URIs are relative to *http://localhost*
|
|
||||||
|
|
||||||
Method | HTTP request | Description
|
|
||||||
------------- | ------------- | -------------
|
|
||||||
[**health_check_handler**](SystemApi.md#health_check_handler) | **GET** /api/v1 |
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## health_check_handler
|
|
||||||
|
|
||||||
> models::HealthResponse health_check_handler()
|
|
||||||
|
|
||||||
|
|
||||||
### Parameters
|
|
||||||
|
|
||||||
This endpoint does not need any parameter.
|
|
||||||
|
|
||||||
### Return type
|
|
||||||
|
|
||||||
[**models::HealthResponse**](HealthResponse.md)
|
|
||||||
|
|
||||||
### Authorization
|
|
||||||
|
|
||||||
No authorization required
|
|
||||||
|
|
||||||
### HTTP request headers
|
|
||||||
|
|
||||||
- **Content-Type**: Not defined
|
|
||||||
- **Accept**: application/json
|
|
||||||
|
|
||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
|
||||||
|
|
@@ -1,13 +0,0 @@
|
|||||||
# TemplateErr
|
|
||||||
|
|
||||||
## Enum Variants
|
|
||||||
|
|
||||||
| Name | Description |
|
|
||||||
|---- | -----|
|
|
||||||
| TemplateErrBadRequest | |
|
|
||||||
| TemplateErrInternalServerError | |
|
|
||||||
| TemplateErrNotFound | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# TemplateErrBadRequest
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**bad_request** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# TemplateErrInternalServerError
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**internal_server_error** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,11 +0,0 @@
|
|||||||
# TemplateErrNotFound
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**not_found** | **String** | |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# UploadBlockParams
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**file_hash** | **String** | File hash associated with the block |
|
|
||||||
**idx** | **i64** | Block index within the file |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
@@ -1,12 +0,0 @@
|
|||||||
# UserBlockInfo
|
|
||||||
|
|
||||||
## Properties
|
|
||||||
|
|
||||||
Name | Type | Description | Notes
|
|
||||||
------------ | ------------- | ------------- | -------------
|
|
||||||
**hash** | **String** | Block hash |
|
|
||||||
**size** | **i64** | Block size in bytes |
|
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
|
||||||
|
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user