use anyhow::Context;
use wasmer_backend_api::WasmerClient;
use super::AsyncCliCommand;
use crate::{config::WasmerEnv, edge_config::EdgeConfig};
#[derive(clap::Parser, Debug)]
pub struct CmdSsh {
#[clap(flatten)]
env: WasmerEnv,
#[clap(long, default_value = "22")]
pub ssh_port: u16,
#[clap(long, default_value = "root.wasmer.network")]
pub host: String,
#[clap(long)]
pub map_port: Vec<u16>,
#[clap(index = 1, default_value = "sharrattj/bash")]
pub run: String,
#[clap(index = 2, trailing_var_arg = true)]
pub run_args: Vec<String>,
#[clap(short, long)]
pub print: bool,
}
#[async_trait::async_trait]
impl AsyncCliCommand for CmdSsh {
type Output = ();
async fn run_async(self) -> Result<(), anyhow::Error> {
let mut config = crate::edge_config::load_config(None)?;
let client = self.env.client()?;
let (token, is_new) = acquire_ssh_token(&client, &config.config).await?;
let host = self.host;
let port = self.ssh_port;
if is_new {
eprintln!("Acquired new SSH token");
config.set_ssh_token(token.clone())?;
if let Err(err) = config.save() {
eprintln!("Warning: failed to save config: {err}");
}
}
let mut cmd = std::process::Command::new("ssh");
let mut cmd = cmd
.args([
"-o",
"ControlPath=none",
"-o",
"StrictHostKeyChecking=no",
"-o",
"UserKnownHostsFile=/dev/null",
"-o",
"IdentitiesOnly=yes",
"-q",
])
.args(["-p", format!("{port}").as_str()]);
for map_port in self.map_port {
cmd = cmd.args(["-L", format!("{map_port}:localhost:{map_port}").as_str()]);
}
cmd = cmd.arg(format!("{token}@{host}")).arg(self.run.as_str());
for run_arg in self.run_args {
cmd = cmd.arg(&run_arg);
}
if self.print {
print!("ssh");
for arg in cmd.get_args() {
print!(" {}", arg.to_string_lossy().as_ref());
}
println!();
return Ok(());
}
let exit = cmd.spawn()?.wait()?;
if exit.success() {
Ok(())
} else {
Err(anyhow::anyhow!("ssh failed with status {}", exit))
}
}
}
type IsNew = bool;
type RawToken = String;
async fn acquire_ssh_token(
client: &WasmerClient,
config: &EdgeConfig,
) -> Result<(RawToken, IsNew), anyhow::Error> {
if let Some(token) = &config.ssh_token {
return Ok((token.clone(), false));
}
let token = create_ssh_token(client).await?;
Ok((token, true))
}
async fn create_ssh_token(client: &WasmerClient) -> Result<RawToken, anyhow::Error> {
wasmer_backend_api::query::generate_deploy_config_token_raw(
client,
wasmer_backend_api::query::TokenKind::SSH,
)
.await
.context("Could not create token for ssh access")
}