rufish 0.3.0

An asynchronous Redfish client library for BMC/server management in Rust.
Documentation
# rufish

[![Crates.io](https://img.shields.io/crates/v/rufish.svg)](https://crates.io/crates/rufish)
[![Docs.rs](https://docs.rs/rufish/badge.svg)](https://docs.rs/rufish)
[![License](https://img.shields.io/crates/l/rufish.svg)](https://github.com/dalof41014/rufish/blob/main/LICENSE-MIT)

**`rufish`** is an asynchronous **Redfish client library** written in Rust for BMC/server out-of-band management via the DMTF Redfish REST API.

---

## Features

* ✅ Async HTTP/HTTPS client (based on `reqwest` + `tokio`)
* ✅ Session-based authentication (X-Auth-Token) with fallback to Basic Auth
***Builder pattern** — inject custom `reqwest::Client`, existing session tokens, or credentials
* ✅ Session persistence — get/set tokens for cross-restart reuse
* ✅ Self-signed certificate support (common for BMCs)
* ✅ Typed Rust structs for all major Redfish resources
* ✅ High-level API for common operations:
  * Systems, Chassis, Managers
  * Power & Thermal monitoring
  * Processors, Memory, Storage, Drives
  * Ethernet Interfaces (System & Manager)
  * Chassis Indicator LED (LocationIndicatorActive / IndicatorLED)
  * Power control (On/Off/Restart/Cycle)
  * Boot override (PXE, BIOS Setup, HDD, etc.)
  * Account management
  * Log services
  * Update & Event services
* ✅ Low-level GET/POST/PATCH/DELETE for any Redfish endpoint
* ✅ Proper error handling with typed errors

---

## Quick Start

```rust
use rufish::RedfishClient;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut client = RedfishClient::new("10.0.0.5", "admin", "password")?;
    client.login().await?;

    // Service Root
    let root = client.get_service_root().await?;
    println!("Redfish version: {:?}", root.redfish_version);

    // List systems
    let systems = client.list_systems().await?;
    println!("Systems: {:?}", systems.members_count);

    // Get system details
    let sys = client.get_system("1").await?;
    println!("Model: {:?}, Power: {:?}", sys.model, sys.power_state);

    // Power control
    client.power_on("1").await?;
    client.graceful_shutdown("1").await?;

    // Thermal monitoring
    let thermal = client.get_thermal("1").await?;
    for t in thermal.temperatures.unwrap_or_default() {
        println!("{}: {}°C", t.name.unwrap_or_default(), t.reading_celsius.unwrap_or(0.0));
    }

    // Boot override
    client.set_boot_pxe("1").await?;

    client.logout().await?;
    Ok(())
}
```

### Builder Pattern

```rust
use rufish::RedfishClient;

// Custom reqwest client (e.g. native-tls, http1_only)
let custom = reqwest::Client::builder()
    .use_native_tls()
    .http1_only()
    .danger_accept_invalid_certs(true)
    .build()?;

let client = RedfishClient::builder("10.0.0.5")
    .credentials("admin", "password")
    .client(custom)
    .build()?;

// Or restore a persisted session (no login needed)
let client = RedfishClient::builder("10.0.0.5")
    .credentials("admin", "password")
    .session("saved-token", "/redfish/v1/SessionService/Sessions/1")
    .build()?;
```

---

## API Summary

### Session & Construction

| Method | Description |
|--------|-------------|
| `new(host, user, pass)` | Create client (HTTPS, accepts self-signed certs) |
| `builder(host)` | Start building a client with custom options |
| `.credentials(user, pass)` | Set credentials (builder) |
| `.client(reqwest_client)` | Inject custom reqwest Client (builder) |
| `.session(token, uri)` | Inject existing session token (builder) |
| `.build()` | Finalize and create the client (builder) |
| `set_session(token, uri)` | Inject session token after construction |
| `session_token()` | Get current token (for persistence) |
| `session_uri()` | Get current session URI |
| `login()` | Establish Redfish session |
| `logout()` | Close session |

### Resources (GET)

| Method | Description |
|--------|-------------|
| `get_service_root()` | Service Root |
| `list_systems()` / `get_system(id)` | Computer Systems |
| `list_chassis()` / `get_chassis(id)` | Chassis |
| `list_managers()` / `get_manager(id)` | Managers (BMC) |
| `get_power(chassis_id)` | Power readings & supplies |
| `get_thermal(chassis_id)` | Temperatures & fans |
| `list_processors(sys)` / `get_processor(sys, id)` | CPUs |
| `list_memory(sys)` / `get_memory(sys, id)` | DIMMs |
| `list_storage(sys)` / `get_storage(sys, id)` | Storage controllers |
| `list_ethernet_interfaces(sys)` | NICs |
| `list_manager_ethernet_interfaces(mgr)` | BMC network interfaces |
| `get_manager_ethernet_interface(mgr, id)` | Get BMC NIC details |
| `patch_manager_ethernet_interface(mgr, id, body)` | Update BMC NIC settings |
| `get_chassis_indicator(chassis)` | Get Indicator LED state |
| `get_account_service()` / `list_accounts()` | User accounts |
| `get_update_service()` | Firmware update service |
| `get_event_service()` | Event subscriptions |
| `list_log_entries(mgr, log)` | Log entries |

### Actions (POST/PATCH)

| Method | Description |
|--------|-------------|
| `reset_system(id, type)` | Reset with any ResetType |
| `power_on(id)` | Power on |
| `power_off(id)` | Force power off |
| `graceful_shutdown(id)` | ACPI shutdown |
| `graceful_restart(id)` | Graceful restart |
| `force_restart(id)` | Force restart |
| `power_cycle(id)` | Power cycle |
| `set_boot_override(id, target, enabled)` | Set boot source |
| `set_boot_pxe(id)` | Boot to PXE |
| `set_boot_bios(id)` | Boot to BIOS Setup |
| `set_chassis_indicator(chassis, on)` | Set Indicator LED on/off |
| `reset_manager(id, type)` | Reset BMC |
| `clear_log(mgr, log)` | Clear log service |

### Virtual Media

| Method | Description |
|--------|-------------|
| `list_virtual_media(mgr)` | List virtual media devices |
| `get_virtual_media(mgr, id)` | Get virtual media status |
| `insert_media(mgr, id, url)` | Mount ISO/IMG from URL |
| `eject_media(mgr, id)` | Unmount media |

### BIOS & Secure Boot

| Method | Description |
|--------|-------------|
| `get_bios(sys)` | Get current BIOS attributes |
| `get_bios_settings(sys)` | Get pending BIOS settings |
| `set_bios_attributes(sys, attrs)` | Set BIOS attributes (next boot) |
| `get_secure_boot(sys)` | Get Secure Boot status |
| `set_secure_boot(sys, enabled)` | Enable/disable Secure Boot |

### Storage / RAID

| Method | Description |
|--------|-------------|
| `list_volumes(sys, storage)` | List RAID volumes |
| `get_volume(sys, storage, vol)` | Get volume details |
| `create_volume(sys, storage, body)` | Create RAID volume |
| `delete_volume(sys, storage, vol)` | Delete volume |
| `get_drive(path)` | Get drive details |

### Network & Certificates

| Method | Description |
|--------|-------------|
| `get_network_protocol(mgr)` | BMC network services (NTP, SSH, etc.) |
| `set_network_protocol(mgr, settings)` | Update network settings |
| `list_serial_interfaces(mgr)` | List serial interfaces |
| `list_certificates(mgr)` | List HTTPS certificates |
| `replace_certificate(path, pem, type)` | Replace certificate |

### Event Subscriptions

| Method | Description |
|--------|-------------|
| `list_subscriptions()` | List event subscriptions |
| `create_subscription(dest, types, ctx)` | Create subscription |
| `delete_subscription(id)` | Delete subscription |

### Firmware Update

| Method | Description |
|--------|-------------|
| `list_firmware_inventory()` | List firmware items |
| `get_firmware_item(id)` | Get firmware version info |
| `simple_update(image_uri)` | Trigger firmware update from URI |

### Tasks & Pagination

| Method | Description |
|--------|-------------|
| `list_tasks()` | List async tasks |
| `get_task(id)` | Get task status |
| `wait_task(id, timeout_secs)` | Poll until task completes |
| `get_all_members(path)` | Auto-follow @odata.nextLink |

### Raw Access

| Method | Description |
|--------|-------------|
| `get(path)` | GET any endpoint (returns `serde_json::Value`) |
| `post(path, body)` | POST action |
| `patch(path, body)` | PATCH resource |
| `delete(path)` | DELETE resource |

---

## Dependencies

```toml
[dependencies]
reqwest = { version = "0.12", features = ["json", "rustls-tls"], default-features = false }
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
thiserror = "1"
log = "0.4"
```

---

## License

MIT OR Apache-2.0