97 lines
3.5 KiB
Rust
97 lines
3.5 KiB
Rust
//! Co-validation shim: prove that `HetznerCloudProvider` can replace lian-build's
|
|
//! bespoke Hetzner spawn/status/destroy without touching the trait.
|
|
//!
|
|
//! Run with:
|
|
//! ```sh
|
|
//! HCLOUD_TOKEN=... HETZNER_DEFAULT_LOCATION=nbg1 \
|
|
//! cargo run --example lian-build-hetzner-shim --features hetzner
|
|
//! ```
|
|
|
|
use std::time::Duration;
|
|
|
|
use ontoref_compute::sizing::{hetzner_tier, tier_spec, SizeTier};
|
|
use ontoref_compute::{ComputeProvider, InstanceSpec, InstanceStatus};
|
|
|
|
#[cfg(not(feature = "hetzner"))]
|
|
compile_error!(
|
|
"This example requires the `hetzner` feature. \
|
|
Run: cargo run --example lian-build-hetzner-shim --features hetzner"
|
|
);
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// 1. Construct provider from environment (same env lian-build uses).
|
|
#[cfg(feature = "hetzner")]
|
|
{
|
|
use ontoref_compute::providers::hetzner::HetznerCloudProvider;
|
|
use tokio::time::sleep;
|
|
|
|
let provider = HetznerCloudProvider::from_env()?;
|
|
|
|
// 2. Pick a tier via the lian-build policy shim (lives here in the example;
|
|
// in real lian-build it lives in lian-build/src/sizing_policy.rs).
|
|
let tier = language_default("rust");
|
|
let ts = tier_spec(tier);
|
|
println!(
|
|
"tier={tier:?} hetzner_type={} cpu={} mem_mb={}",
|
|
hetzner_tier(tier),
|
|
ts.vcpus,
|
|
ts.memory_mb
|
|
);
|
|
|
|
// 3. Build a spec using the constructor (InstanceSpec is #[non_exhaustive]).
|
|
let mut spec = InstanceSpec::new("ubuntu-24.04", ts.vcpus, ts.memory_mb as u32);
|
|
spec.disk_gb = ts.disk_gb as u32;
|
|
// location: None picks up HETZNER_DEFAULT_LOCATION from provider env.
|
|
spec.ssh_key = std::env::var("SSH_KEY_NAME").ok();
|
|
spec.labels.insert("lian-build/shim".into(), "true".into());
|
|
spec.ttl = Some(Duration::from_secs(15 * 60));
|
|
spec.timeout = Some(Duration::from_secs(60));
|
|
|
|
// 4. Spawn.
|
|
let inst = provider.spawn(spec).await?;
|
|
println!("spawned: handle={} ip={:?}", inst.handle, inst.ip);
|
|
|
|
// 5. Poll status until Running or 2 minutes elapse.
|
|
let started = std::time::Instant::now();
|
|
loop {
|
|
let s = provider.status(&inst.handle).await?;
|
|
println!(" status={s:?}");
|
|
match s {
|
|
InstanceStatus::Running => break,
|
|
InstanceStatus::Failed
|
|
| InstanceStatus::Succeeded
|
|
| InstanceStatus::Unknown => break,
|
|
_ if started.elapsed() > Duration::from_secs(120) => {
|
|
eprintln!("timed out waiting for Running");
|
|
break;
|
|
}
|
|
_ => sleep(Duration::from_secs(5)).await,
|
|
}
|
|
}
|
|
|
|
// 6. Destroy.
|
|
provider.destroy(&inst.handle).await?;
|
|
println!("destroyed: {}", inst.handle);
|
|
|
|
// 7. Verify destroy is idempotent (same call lian-build's cleanup makes).
|
|
provider.destroy(&inst.handle).await?;
|
|
println!("idempotent destroy: ok");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Maps a language identifier to a `SizeTier`.
|
|
///
|
|
/// This function lives in lian-build in production. It is inlined here to
|
|
/// demonstrate that `language_default` is NOT part of `ontoref-compute` — it is
|
|
/// build-system policy that belongs in the caller.
|
|
fn language_default(language: &str) -> SizeTier {
|
|
match language {
|
|
"rust" | "cpp" | "scala" => SizeTier::M,
|
|
"python" | "ruby" | "javascript" => SizeTier::S,
|
|
"nu" | "bash" | "sh" => SizeTier::XS,
|
|
_ => SizeTier::S,
|
|
}
|
|
}
|