|
1 | 1 | //! Live-server test harness for the coold/broker/builder stack. |
2 | 2 | //! |
3 | | -//! Tests are written as Rust integration tests under `tests/`, marked |
4 | | -//! `#[ignore]` so default `cargo test` skips them. Run with: |
| 3 | +//! Tests are Rust integration tests under `tests/`, marked `#[ignore]` so |
| 4 | +//! default `cargo test` skips them. Every suite provisions its own |
| 5 | +//! ephemeral Hetzner cluster via [`hetzner::EphemeralCluster`], runs |
| 6 | +//! `coolify init apply` from the local `coolify` binary, then exercises |
| 7 | +//! the black-box HTTP/UDS/systemd contract over SSH. No broker/coold |
| 8 | +//! code is linked. |
| 9 | +//! |
| 10 | +//! Run with: |
5 | 11 | //! |
6 | 12 | //! ```text |
7 | | -//! BUILDER_HOST=<host-a> \ |
8 | | -//! COOLD_ONLY_HOST=<host-b> \ |
9 | | -//! BUILDER_MGMT=<wg0-ip-of-host-a> \ |
10 | | -//! COOLD_ONLY_MGMT=<wg0-ip-of-host-b> \ |
11 | | -//! CENTRAL_HOST=<central-host> \ |
| 13 | +//! HETZNER_TOKEN=... HETZNER_PROJECT=... \ |
12 | 14 | //! SSH_KEY=~/.ssh/<key> \ |
13 | | -//! cargo test -p e2e-tests -- --ignored --test-threads=1 |
| 15 | +//! COOLIFY_BIN=$(which coolify) \ |
| 16 | +//! cargo test -p e2e-tests -- --ignored --nocapture --test-threads=1 |
14 | 17 | //! ``` |
15 | 18 | //! |
16 | | -//! `--test-threads=1` is mandatory: the tests dispatch real builds against |
17 | | -//! a shared cluster, and running them in parallel overwhelms the |
18 | | -//! `COOLD_BUILDER_CAPACITY` semaphore and races on `buildah images` state |
19 | | -//! shared across hosts. |
20 | | -//! |
21 | | -//! The harness drives the broker UDS via `ssh + curl --unix-socket` on the |
22 | | -//! central host and asserts remote state via `buildah images` and |
23 | | -//! `systemctl is-active`. No broker/coold code is linked — tests exercise |
24 | | -//! the black-box contract. |
| 19 | +//! `--test-threads=1` is mandatory: the tests dispatch real builds and |
| 20 | +//! running them in parallel overwhelms the `COOLD_BUILDER_CAPACITY` |
| 21 | +//! semaphore and races on `buildah images` state shared across hosts. |
| 22 | +//! VMs are deleted via [`hetzner::EphemeralCluster`]'s RAII `Drop`, so |
| 23 | +//! panics during assertions still clean up paid resources. |
25 | 24 |
|
26 | 25 | use std::process::Command; |
27 | 26 | use std::thread; |
@@ -90,37 +89,34 @@ pub struct Env { |
90 | 89 | pub cool_only_mgmt: String, |
91 | 90 | pub central_host: String, |
92 | 91 | pub ssh_key: String, |
93 | | - pub ssh_user: String, |
94 | 92 | } |
95 | 93 |
|
96 | 94 | impl Env { |
97 | | - pub fn from_env() -> Self { |
98 | | - load_dotenv(); |
| 95 | + /// Build from a live [`hetzner::EphemeralCluster`] provisioned with |
| 96 | + /// 2 hosts (A = central + builder, B = coold-only) and the wg0 |
| 97 | + /// addresses resolved on each host after `coolify init apply`. |
| 98 | + pub fn from_cluster( |
| 99 | + cluster: &hetzner::EphemeralCluster, |
| 100 | + builder_mgmt: String, |
| 101 | + cool_only_mgmt: String, |
| 102 | + ) -> Self { |
| 103 | + let hosts = cluster.hosts(); |
| 104 | + assert_eq!(hosts.len(), 2, "Env::from_cluster expects 2 hosts"); |
99 | 105 | Self { |
100 | | - builder_host: must("BUILDER_HOST"), |
101 | | - cool_only_host: must("COOLD_ONLY_HOST"), |
102 | | - builder_mgmt: must("BUILDER_MGMT"), |
103 | | - cool_only_mgmt: must("COOLD_ONLY_MGMT"), |
104 | | - central_host: must("CENTRAL_HOST"), |
105 | | - ssh_key: must("SSH_KEY"), |
106 | | - ssh_user: std::env::var("SSH_USER").unwrap_or_else(|_| "root".into()), |
| 106 | + builder_host: hosts[0].ipv4.clone(), |
| 107 | + cool_only_host: hosts[1].ipv4.clone(), |
| 108 | + builder_mgmt, |
| 109 | + cool_only_mgmt, |
| 110 | + central_host: hosts[0].ipv4.clone(), |
| 111 | + ssh_key: cluster.ssh_key.clone(), |
107 | 112 | } |
108 | 113 | } |
109 | 114 |
|
110 | 115 | pub fn ssh(&self, host: &str, cmd: &str) -> Result<String, String> { |
111 | 116 | let out = Command::new("ssh") |
112 | | - .args([ |
113 | | - "-i", |
114 | | - &self.ssh_key, |
115 | | - "-o", |
116 | | - "StrictHostKeyChecking=accept-new", |
117 | | - "-o", |
118 | | - "BatchMode=yes", |
119 | | - "-o", |
120 | | - "ConnectTimeout=10", |
121 | | - &format!("{}@{}", self.ssh_user, host), |
122 | | - cmd, |
123 | | - ]) |
| 117 | + .args(hetzner::ephemeral_ssh_args(&self.ssh_key)) |
| 118 | + .arg(format!("root@{host}")) |
| 119 | + .arg(cmd) |
124 | 120 | .output() |
125 | 121 | .map_err(|e| format!("spawn ssh: {e}"))?; |
126 | 122 | if !out.status.success() { |
@@ -267,10 +263,6 @@ impl Env { |
267 | 263 | } |
268 | 264 | } |
269 | 265 |
|
270 | | -fn must(key: &str) -> String { |
271 | | - std::env::var(key).unwrap_or_else(|_| panic!("env {key} required")) |
272 | | -} |
273 | | - |
274 | 266 | /// Lowercase request_id suitable for use as an OCI image tag (OCI rejects |
275 | 267 | /// uppercase in repository names). |
276 | 268 | pub fn uniq_req_id(prefix: &str) -> String { |
|
0 commit comments