crates/mock-scaleway/src/bin/mock-scaleway.rs
//! `mock-scaleway` — run the fake Scaleway Instance API.
//!
//! mock-scaleway --port 8098 # faithful timings (poweron 30 s)
//! mock-scaleway --port 8098 --speed 0 # virtual time: each request moves it 5 s
//! mock-scaleway --port 8098 --arm out-of-stock # behaviour 3, sticky
//! mock-scaleway --port 8098 --token scw-… # the secret key it accepts (repeatable)
//! mock-scaleway --port 8098 --engine container --bin-dir <dir> --by-hostname PREFIX=MEDIUM.json
//!
//! `--speed` is in thousandths (`1000` = real time), as mock-upcloud's.
use std::sync::Arc;
use mock_scaleway::{Fault, Mock};
use mock_upcloud::container::{ContainerEngine, Medium, Settings};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut port: u16 = 8098;
let mut speed: u64 = 1000;
let mut listen = "127.0.0.1".to_string();
let mut tokens: Vec<String> = Vec::new();
let mut arm: Vec<Fault> = Vec::new();
let mut engine: Option<String> = None;
let mut loopback: Option<(u8, u8)> = None;
let mut cs = Settings {
root: std::path::PathBuf::from("/tmp/mock-scaleway-container"),
network: String::new(),
image: "docker.io/library/alpine:3".into(),
bin_dir: std::path::PathBuf::new(),
by_hostname: Vec::new(),
label: String::new(),
engine: "podman".into(),
utility: mock_upcloud::container::MOCK_UTILITY.into(),
};
let mut args = std::env::args().skip(1);
while let Some(a) = args.next() {
let mut val = |what: &str| args.next().ok_or_else(|| format!("{a} wants {what}"));
match a.as_str() {
"--port" => port = val("a number")?.parse()?,
"--speed" => speed = val("a number")?.parse()?,
"--listen" => listen = val("an address")?,
"--token" => tokens.push(val("a secret key")?),
"--arm" => {
let n = val("a fault name")?;
let (name, ct) = n
.split_once(':')
.map(|(a, b)| (a.to_string(), Some(b.to_string())))
.unwrap_or((n.clone(), None));
arm.push(Fault::parse(&name, ct.as_deref()).ok_or_else(|| {
format!("no such fault: {n} (the faults: out-of-stock[:<commercial_type>])")
})?);
}
"--public-on-loopback" => {
let v = val("A.B")?;
let (x, y) = v.split_once('.').ok_or("--public-on-loopback wants A.B")?;
loopback = Some((x.parse()?, y.parse()?));
}
"--engine" => engine = Some(val("`container`")?),
"--root" => cs.root = val("a directory")?.into(),
"--network" => cs.network = val("a name")?,
"--image" => cs.image = val("an image")?,
"--bin-dir" => cs.bin_dir = val("a directory")?.into(),
"--label" => cs.label = val("a word")?,
"--utility" => cs.utility = val("X.Y")?,
"--by-hostname" => {
let v = val("PREFIX=MEDIUM.json")?;
let (prefix, file) = v
.split_once('=')
.ok_or("--by-hostname wants PREFIX=MEDIUM.json")?;
let m = Medium::parse(&std::fs::read(file).map_err(|e| format!("{file}: {e}"))?)?
.ok_or_else(|| format!("{file} is not a container medium"))?;
cs.by_hostname.push((prefix.to_string(), m));
}
"--help" | "-h" => {
println!("mock-scaleway [--port N] [--speed MILLI] [--listen ADDR] [--token KEY]… [--arm out-of-stock[:TYPE]]… [--public-on-loopback A.B] [--engine container --bin-dir DIR --by-hostname PREFIX=MEDIUM.json …]");
return Ok(());
}
other => return Err(format!("unknown argument {other}").into()),
}
}
let mock = Mock::new(mock_scaleway::Clock::new(speed));
let mut container: Option<Arc<ContainerEngine>> = None;
if let Some(e) = engine.as_deref() {
if e != "container" {
return Err(format!("--engine {e}: the engine is `container`").into());
}
if cs.label.is_empty() {
cs.label = format!("msw{}", std::process::id());
}
if cs.network.is_empty() {
cs.network = format!("mock-scaleway-{}", cs.label);
}
let ce = Arc::new(ContainerEngine::new(cs.clone()));
ce.ensure_network()?;
container = Some(ce);
}
mock.with(|a| {
if !tokens.is_empty() {
a.tokens = tokens.clone();
}
a.faults = arm.clone();
a.public_on_loopback = loopback.or(container.as_ref().map(|_| (81, 0)));
a.utility = cs.utility.clone();
if let Some(ce) = &container {
a.engine = Some(ce.clone() as Arc<dyn mock_upcloud::kvm::GuestEngine>);
}
});
let p = mock.serve_on(&format!("{listen}:{port}"))?;
println!(
"mock-scaleway on http://{listen}:{p} (speed {speed}/1000, key {})",
if tokens.is_empty() {
mock_scaleway::TOKEN.to_string()
} else {
format!("{} given", tokens.len())
}
);
loop {
std::thread::sleep(std::time::Duration::from_secs(3600));
}
}