wash_lib/start/wadm.rs
1use anyhow::{Context as _, Result};
2use command_group::AsyncCommandGroup;
3use std::path::{Path, PathBuf};
4use std::process::Stdio;
5use tokio::fs::metadata;
6use tokio::process::{Child, Command};
7use tracing::warn;
8
9use super::download_binary_from_github;
10use crate::common::CommandGroupUsage;
11
12const WADM_GITHUB_RELEASE_URL: &str = "https://github.com/wasmcloud/wadm/releases/download";
13pub const WADM_PID: &str = "wadm.pid";
14#[cfg(target_family = "unix")]
15pub const WADM_BINARY: &str = "wadm";
16#[cfg(target_family = "windows")]
17pub const WADM_BINARY: &str = "wadm.exe";
18
19/// Downloads the wadm binary for the architecture and operating system of the current host machine.
20///
21/// # Arguments
22///
23/// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z`
24/// * `dir` - Where to download the `wadm` binary to
25/// # Examples
26///
27/// ```no_run
28/// # #[tokio::main]
29/// # async fn main() {
30/// use wash_lib::start::ensure_wadm;
31/// let res = ensure_wadm("v0.4.0-alpha.1", "/tmp/").await;
32/// assert!(res.is_ok());
33/// assert!(res.unwrap().to_string_lossy() == "/tmp/wadm");
34/// # }
35/// ```
36pub async fn ensure_wadm<P>(version: &str, dir: P) -> Result<PathBuf>
37where
38 P: AsRef<Path>,
39{
40 ensure_wadm_for_os_arch_pair(std::env::consts::OS, std::env::consts::ARCH, version, dir).await
41}
42
43/// Ensures the `wadm` binary is installed, returning the path to the executable early if it exists or
44/// downloading the specified GitHub release version of wadm from <https://github.com/wasmcloud/wadm/releases/>
45/// and unpacking the binary for a specified OS/ARCH pair to a directory. Returns the path to the wadm executable.
46/// # Arguments
47///
48/// * `os` - Specifies the operating system of the binary to download, e.g. `linux`
49/// * `arch` - Specifies the architecture of the binary to download, e.g. `amd64`
50/// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z`
51/// * `dir` - Where to download the `wadm` binary to
52/// # Examples
53///
54/// ```no_run
55/// # #[tokio::main]
56/// # async fn main() {
57/// use wash_lib::start::ensure_wadm_for_os_arch_pair;
58/// let os = std::env::consts::OS;
59/// let arch = std::env::consts::ARCH;
60/// let res = ensure_wadm_for_os_arch_pair(os, arch, "v0.4.0-alpha.1", "/tmp/").await;
61/// assert!(res.is_ok());
62/// assert!(res.unwrap().to_string_lossy() == "/tmp/wadm");
63/// # }
64/// ```
65pub async fn ensure_wadm_for_os_arch_pair<P>(
66 os: &str,
67 arch: &str,
68 version: &str,
69 dir: P,
70) -> Result<PathBuf>
71where
72 P: AsRef<Path>,
73{
74 let wadm_bin_path = dir.as_ref().join(WADM_BINARY);
75 if let Ok(_md) = metadata(&wadm_bin_path).await {
76 // Check version to see if we need to download new one
77 if let Ok(output) = Command::new(&wadm_bin_path).arg("--version").output().await {
78 let stdout = String::from_utf8_lossy(&output.stdout).to_string();
79 eprintln!("👀 Found wadm version on the disk: {}", stdout.trim_end());
80 let re = regex::Regex::new(r"^wadm[^\s]*").unwrap();
81 if re.replace(&stdout, "").to_string().trim() == version.trim_start_matches('v') {
82 // wadm already exists, return early
83 return Ok(wadm_bin_path);
84 }
85 }
86 }
87 // Download wadm tarball
88 eprintln!(
89 "🎣 Downloading new wadm from {}",
90 &wadm_url(os, arch, version)
91 );
92
93 let res = download_binary_from_github(&wadm_url(os, arch, version), dir, WADM_BINARY).await;
94 if let Ok(ref path) = res {
95 eprintln!("🎯 Saved wadm to {}", path.display());
96 }
97
98 res
99}
100
101/// Downloads the wadm binary for the architecture and operating system of the current host machine.
102///
103/// # Arguments
104///
105/// * `version` - Specifies the version of the binary to download in the form of `vX.Y.Z`
106/// * `dir` - Where to download the `wadm` binary to
107/// # Examples
108///
109/// ```no_run
110/// # #[tokio::main]
111/// # async fn main() {
112/// use wash_lib::start::download_wadm;
113/// let res = download_wadm("v0.4.0-alpha.1", "/tmp/").await;
114/// assert!(res.is_ok());
115/// assert!(res.unwrap().to_string_lossy() == "/tmp/wadm");
116/// # }
117/// ```
118pub async fn download_wadm<P>(version: &str, dir: P) -> Result<PathBuf>
119where
120 P: AsRef<Path>,
121{
122 download_binary_from_github(
123 &wadm_url(std::env::consts::OS, std::env::consts::ARCH, version),
124 dir,
125 WADM_BINARY,
126 )
127 .await
128}
129
130/// Configuration for wadm
131#[derive(Clone)]
132pub struct WadmConfig {
133 /// Whether or not to use structured log output (as JSON)
134 pub structured_logging: bool,
135 /// The NATS JetStream domain to connect to [env: WADM_JETSTREAM_DOMAIN=]
136 pub js_domain: Option<String>,
137 /// The URL of the nats server you want to connect to
138 pub nats_server_url: String,
139 // (Optional) NATS credential file to use when authenticating [env: WADM_NATS_CREDS_FILE=]
140 pub nats_credsfile: Option<PathBuf>,
141}
142
143/// Helper function to execute a wadm binary with optional arguments. This function does not check to see if a
144/// wadm instance is already running or managing a lattice as wadm does not need to be a singleton.
145///
146/// # Arguments
147///
148/// * `state_dir` - Path to the folder in which wadm process state (ex. pidfile) should be stored
149/// * `bin_path` - Path to the wadm binary to execute
150/// * `stderr` - Specify where wadm stderr logs should be written to. If logs aren't important, use `std::process::Stdio::null()`
151/// * `config` - Optional configuration for wadm
152pub async fn start_wadm<T>(
153 state_dir: impl AsRef<Path>,
154 bin_path: impl AsRef<Path>,
155 stderr: T,
156 config: Option<WadmConfig>,
157 command_group: CommandGroupUsage,
158) -> Result<Child>
159where
160 T: Into<Stdio>,
161{
162 let mut cmd = Command::new(bin_path.as_ref());
163 cmd.stderr(stderr).stdin(Stdio::null());
164
165 if let Some(wadm_config) = config {
166 cmd.arg("--nats-server");
167 cmd.arg(wadm_config.nats_server_url);
168 if wadm_config.structured_logging {
169 cmd.arg("--structured-logging");
170 }
171 if let Some(domain) = wadm_config.js_domain.as_ref() {
172 cmd.arg("-d");
173 cmd.arg(domain);
174 }
175 if let Some(credsfile) = wadm_config.nats_credsfile.as_ref() {
176 cmd.arg("--nats-creds-file");
177 cmd.arg(credsfile);
178 }
179 }
180
181 let child = if command_group == CommandGroupUsage::CreateNew {
182 cmd.group_spawn().map_err(anyhow::Error::from)?.into_inner()
183 } else {
184 cmd.spawn().map_err(anyhow::Error::from)?
185 };
186
187 let pid = child
188 .id()
189 .context("unexpectedly missing pid for spawned process")?;
190
191 let pid_path = state_dir.as_ref().join(WADM_PID);
192 if let Err(e) = tokio::fs::write(pid_path, pid.to_string()).await {
193 warn!("Couldn't write wadm pidfile: {e}");
194 }
195
196 Ok(child)
197}
198
199/// Helper function to determine the wadm release path given an os/arch and version
200fn wadm_url(os: &str, arch: &str, version: &str) -> String {
201 // Replace architecture to match wadm release naming scheme
202 let arch = match arch {
203 "x86_64" => "amd64",
204 _ => arch,
205 };
206 format!("{WADM_GITHUB_RELEASE_URL}/{version}/wadm-{version}-{os}-{arch}.tar.gz")
207}