wasmer-deploy-client-cli 0.1.1

CLI client for Wasmer Deploy
Documentation
use anyhow::Context;
use wasmer_api::backend::BackendClient;
use wasmer_deploy_schema::schema::{
    DeploymentV1, WebcPackageIdentifierV1, WorkloadRunnerV1::WebcCommand,
    WorkloadRunnerWebcCommandV1, WorkloadV1,
};

use crate::{config::DeployClientConfig, ApiOpts};

use super::AsyncCliCommand;

/// Open a SSH session on Deploy.
#[derive(clap::Parser, Debug)]
pub struct CmdSsh {
    #[clap(flatten)]
    api: ApiOpts,
}

impl CmdSsh {
    async fn exec(self) -> Result<(), anyhow::Error> {
        let mut config = crate::config::load_config(None)?;
        let client = self.api.client()?;

        let (token, is_new) = acquire_ssh_token(&client, &config.config).await?;

        if is_new {
            eprintln!("Acuired new SSH token");
            config.set_ssh_token(token.clone())?;
            if let Err(err) = config.save() {
                eprintln!("Warning: failed to save config: {err}");
            }
        }
        let exit = std::process::Command::new("ssh")
            .args(&["-o", "ControlPath=none"])
            .arg(format!("{token}@tokera.sh"))
            .arg("sharrattj/dash")
            .spawn()?
            .wait()?;
        if exit.success() {
            Ok(())
        } else {
            Err(anyhow::anyhow!("ssh failed with status {}", exit))
        }
    }
}

type IsNew = bool;
type RawToken = String;

impl AsyncCliCommand for CmdSsh {
    fn run_async(self) -> futures::future::BoxFuture<'static, Result<(), anyhow::Error>> {
        Box::pin(self.exec())
    }
}

async fn acquire_ssh_token(
    client: &BackendClient,
    config: &DeployClientConfig,
) -> Result<(RawToken, IsNew), anyhow::Error> {
    if let Some(token) = &config.ssh_token {
        // TODO: validate that token is still valid.
        return Ok((token.clone(), false));
    }

    let token = create_ssh_token(client).await?;
    Ok((token, true))
}

/// Create a new token for SSH access through the backend API.
async fn create_ssh_token(client: &BackendClient) -> Result<RawToken, anyhow::Error> {
    let name = "ssh-shell";

    let depl = DeploymentV1 {
        name: name.to_string(),
        workload: WorkloadV1 {
            name: None,
            runner: WebcCommand(WorkloadRunnerWebcCommandV1 {
                package: WebcPackageIdentifierV1 {
                    repository: "https://wapm.dev".parse().unwrap(),
                    namespace: "sharrattj".to_string(),
                    name: "dash".to_string(),
                    tag: None,
                },
                command: "dash".to_string(),
            }),
            capabilities: Default::default(),
        },
    };

    let cfg = wasmer_api::backend::publish_deploy_app(
        &client,
        wasmer_api::backend::gql::PublishDeployAppVariables {
            config: serde_json::to_string(&depl)?,
            name: name.into(),
            owner: None,
        },
    )
    .await
    .context("could not publish deployment")?;

    wasmer_api::backend::generate_deploy_token_raw(&client, cfg.id.into_inner())
        .await
        .context("Could not create token for ssh access")
}