Files
harmony/opnsense-api/examples/firmware_upgrade.rs
Jean-Gabriel Gill-Couture c926ff3c4b chore: warning sweep — manual cleanup of remaining 105 → 0
Picks up where the auto-fix pass left off. Workspace warning count
goes from 105 to 0 across `cargo build --workspace --all-targets`.

Three categories of fixes:

1. Mechanical fixes the auto-pass couldn't handle (unused imports
   inside braced multi-name `use` statements, unused variables that
   needed an underscore prefix without breaking other references):
   batched via a small Python script, then 6 manual edits where the
   warning location and the actual identifier were on different
   lines.

2. Dead-code that's intentionally kept around for future wiring or
   debug visibility — `#[allow(dead_code)]` at the right scope:
   - 19 individual items (struct fields, methods, free functions,
     type aliases, enum variants), e.g. `default_namespace` / `default_cluster_issuer`
     in zitadel/mod.rs (used via serde defaults, opaque to rustc),
     `score` fields on the OKD bootstrap interpret types,
     `crd_exists` methods on the prometheus alerting scores, the
     `harmony_inventory_agent::local_presence::{DiscoveryEvent,
     discover_agents}` re-exports.
   - 5 module-level allows for files where most items are
     aspirational scaffolding (harmony_agent's replica workflow,
     opnsense-config dnsmasq, three opnsense-api examples).

3. Special cases that needed real fixes, not allows:
   - `opnsense-config-xml/src/data/haproxy.rs`: deprecated
     `rand::thread_rng` / `Rng::gen` updated to `rng()` / `random`.
   - `harmony_secret/src/lib.rs`: the `secrete2etest` integration
     test gate is now declared in Cargo.toml's `[lints.rust]
     unexpected_cfgs.check-cfg`; the gated test module is structured
     so its dead `TestSecret`/`TestUserMeta` types come along for
     the cfg ride and don't show up as unconditional dead code.
   - `harmony/src/modules/nats/score_nats_k8s.rs:241`: `K8sIngressScore
     { name: todo!(), ... }`'s unreachable expression annotated.
   - `harmony/src/domain/topology/k8s_anywhere/k8s_anywhere.rs:982`:
     wrap the dead-after-`return Ok(Noop)` branch in
     `#[allow(unreachable_code)] { ... }`. Behavior unchanged.
   - `examples/try_rust_webapp/Cargo.toml`: `autobins = false` so
     `src/main.rs` isn't auto-registered as both bin AND example.

All 16 lib-test suites pass: 437 tests, 0 failed, 13 ignored.

Ready for `-Dwarnings` in CI as a follow-up — the gate makes
sense once we're sure no contributor's local builds slip warnings
back in.
2026-05-06 23:09:12 -04:00

149 lines
4.6 KiB
Rust

#![allow(dead_code)]
//! Example: trigger OPNsense firmware upgrade and monitor progress.
//!
//! ```text
//! cargo run --example firmware_upgrade
//! ```
//!
//! Calls `POST /api/core/firmware/update` to trigger a major update,
//! then polls `GET /api/core/firmware/upgradestatus` for progress.
mod common;
use serde::Deserialize;
#[derive(Debug, Deserialize)]
struct UpdateResponse {
#[serde(default)]
status: String,
#[serde(default)]
msg_uuid: String,
}
#[derive(Debug, Deserialize)]
struct UpgradeStatus {
#[serde(default)]
status: String,
#[serde(default)]
log: String,
}
#[tokio::main]
async fn main() {
let client = common::client_from_env();
// Step 1: Check for updates
println!("Checking for firmware updates...");
let check: serde_json::Value = client
.get_typed("core", "firmware", "status")
.await
.expect("status call failed");
let status = check
.get("status")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let status_msg = check
.get("status_msg")
.and_then(|v| v.as_str())
.unwrap_or("");
println!(" status: {status}");
println!(" message: {status_msg}");
if let Some(upgrade) = check.get("upgrade_packages") {
println!(
" upgrade packages: {}",
serde_json::to_string_pretty(upgrade).unwrap()
);
}
if status == "none" {
println!("\nNo status available. Running firmware check first...");
let check_resp: UpdateResponse = client
.post_typed("core", "firmware", "check", None::<&()>)
.await
.expect("check call failed");
println!(" check triggered: {check_resp:?}");
// Wait for check to complete
for _ in 0..30 {
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
let status: UpgradeStatus = client
.get_typed("core", "firmware", "upgradestatus")
.await
.expect("upgradestatus failed");
let last_line = status.log.lines().last().unwrap_or("");
println!(" check status={}, last_log={}", status.status, last_line);
if status.status == "done" {
break;
}
}
// Re-check status
let check2: serde_json::Value = client
.get_typed("core", "firmware", "status")
.await
.expect("status call failed");
let status2 = check2
.get("status")
.and_then(|v| v.as_str())
.unwrap_or("unknown");
let msg2 = check2
.get("status_msg")
.and_then(|v| v.as_str())
.unwrap_or("");
println!("\n Updated status: {status2}");
println!(" Updated message: {msg2}");
if status2 == "update" {
println!("\nUpdates available. Triggering firmware update...");
} else {
println!("\nNo updates available (status={status2}). Exiting.");
return;
}
} else if status != "update" {
println!("\nFirmware status is '{status}', not 'update'. Exiting.");
return;
} else {
println!("\nUpdates available. Triggering firmware update...");
}
// Step 2: Trigger the update
let update: UpdateResponse = client
.post_typed("core", "firmware", "update", None::<&()>)
.await
.expect("update call failed");
println!(" update triggered: {update:?}");
// Step 3: Poll for completion
println!("\nMonitoring upgrade progress...");
for i in 0..300 {
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
let status = client
.get_typed::<UpgradeStatus>("core", "firmware", "upgradestatus")
.await;
match status {
Ok(s) => {
let last_lines: Vec<&str> = s.log.lines().rev().take(3).collect();
println!(
"[{i:3}] status={}, log tail: {}",
s.status,
last_lines.into_iter().rev().collect::<Vec<_>>().join(" | ")
);
if s.status == "done" || s.status == "reboot" {
println!("\nFirmware upgrade complete! Status: {}", s.status);
if s.status == "reboot" {
println!("A reboot is required to complete the upgrade.");
}
break;
}
}
Err(e) => {
println!("[{i:3}] Connection error (firewall may be rebooting): {e}");
}
}
}
}