wasmer-deploy-cli 0.1.29

CLI for Wasmer Deploy
Documentation
use anyhow::{bail, Context};
use wasmer_api::backend::gql::DeployApp;
use wasmer_deploy_schema::schema::AppConfigV1;

use crate::{cmd::AsyncCliCommand, ApiOpts, ItemFormatOpts};

use super::AppIdent;

/// Show an app.
#[derive(clap::Parser, Debug)]
pub struct CmdAppGet {
    #[clap(flatten)]
    api: ApiOpts,
    #[clap(flatten)]
    fmt: ItemFormatOpts,

    /// Identifier of the app.
    ///
    /// Eg:
    /// - name (assumes current user)
    /// - namespace/name
    ///
    /// NOTE: if not specified, the command will try to infer the app from an
    /// app.yaml file in the current directory.
    ident: Option<AppIdent>,
}

impl CmdAppGet {
    async fn run(self) -> Result<(), anyhow::Error> {
        let app = self.get_app().await?;
        println!("{}", self.fmt.format.render(&app));
        Ok(())
    }

    async fn get_app(&self) -> Result<DeployApp, anyhow::Error> {
        let client = self.api.client()?;

        let app = if let Some(ident) = &self.ident {
            ident.resolve(&client).await?
        } else {
            let (config, app_config_path) = Self::get_app_config_from_current_dir()?;
            eprintln!("Loaded app config from: {}", app_config_path.display());

            if let Some(app_id) = config.app_id {
                AppIdent::AppId(app_id.clone())
                    .resolve(&client)
                    .await
                    .with_context(|| format!("Could not find app with id '{}'", app_id))?
            } else {
                // scanning for all apps for my unique app's name
                let user_accessible_apps =
                    wasmer_api::backend::user_accessible_apps(&client).await?;

                // find the app with the same name as the one in the app.yaml, because we don't have an ID and app names are unique
                let apps = user_accessible_apps
                    .iter()
                    .filter(|app| app.name == config.name)
                    .collect::<Vec<_>>();

                if apps.len() > 1 {
                    let names = apps
                        .iter()
                        .map(|app| format!("{}/{}", app.owner.global_name, app.name))
                        .collect::<Vec<_>>()
                        .join(",");
                    bail!(
                        "Found multiple applicable with the name '{}': {}",
                        config.name,
                        names
                    );
                }

                apps.get(0)
                    .map(|x| (**x).clone())
                    .with_context(|| format!("Could not find app named '{}'", config.name))?
            }
        };

        Ok(app)
    }

    fn get_app_config_from_current_dir() -> Result<(AppConfigV1, std::path::PathBuf), anyhow::Error>
    {
        // read the information from local `app.yaml
        let current_dir = std::env::current_dir()?;
        let app_config_path = current_dir.join(AppConfigV1::CANONICAL_FILE_NAME);

        if !app_config_path.exists() || !app_config_path.is_file() {
            bail!(
            "Could not find app.yaml at path: '{}'.\nPlease specify an app like 'wasmer app get <namespace>/<name>' or 'wasmer app get <name>`'",
            app_config_path.display()
        );
        }
        // read the app.yaml
        let raw_app_config = std::fs::read_to_string(&app_config_path)
            .with_context(|| format!("Could not read file '{}'", app_config_path.display()))?;

        // parse the app.yaml
        let config = AppConfigV1::parse_yaml(&raw_app_config)
            .map_err(|err| anyhow::anyhow!("Could not parse app.yaml: {err:?}"))?;

        Ok((config, app_config_path))
    }
}

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

#[derive(clap::Parser, Debug)]
pub struct CmdAppInfo {
    #[clap(flatten)]
    api: ApiOpts,
    #[clap(flatten)]
    fmt: ItemFormatOpts,

    /// Identifier of the app.
    ///
    /// Eg:
    /// - name (assumes current user)
    /// - namespace/name
    ident: Option<AppIdent>,
}

impl CmdAppInfo {
    async fn run(self) -> Result<(), anyhow::Error> {
        let cmd_app_get = CmdAppGet {
            api: self.api,
            fmt: self.fmt,
            ident: self.ident,
        };
        let app = cmd_app_get.get_app().await?;

        let app_url = app.url;
        let versioned_url = app.active_version.url;
        let dashboard_url = app.admin_url;

        eprintln!("  App Info  ");
        eprintln!("> App Name: {}", app.name);
        eprintln!("> App URL: {}", app_url);
        eprintln!("> Versioned URL: {}", versioned_url);
        eprintln!("> Admin dashboard: {}", dashboard_url);

        Ok(())
    }
}

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