1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
mod types;

pub mod cmd;
pub mod config;
pub mod util;

use clap::Parser;
use cmd::CliCommand;
use tracing_subscriber::{fmt, layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

pub fn run() -> Result<(), anyhow::Error> {
    let args = Args::parse();
    initialize_logging(&args);
    args.cmd.run()
}

#[derive(clap::Parser, Debug)]
#[clap(about, version)]
struct Args {
    #[clap(flatten)]
    pub verbosity: clap_verbosity_flag::Verbosity<clap_verbosity_flag::WarnLevel>,
    #[clap(subcommand)]
    cmd: cmd::SubCmd,
}

#[derive(clap::Parser, Debug, Clone)]
pub struct ApiOpts {
    #[clap(long, env = "WASMER_TOKEN")]
    pub token: Option<String>,
    #[clap(long)]
    pub registry: Option<url::Url>,
}

impl ApiOpts {
    const DEV_REGISTRY: &'static str = "https://registry.wapm.dev/graphql";

    fn user_token_from_config(registry: &str) -> Result<Option<String>, anyhow::Error> {
        let wasmer_dir = wasmer_registry::WasmerConfig::get_wasmer_dir()
            .map_err(|e| anyhow::anyhow!("no wasmer dir: {e}"))?;
        let config = wasmer_registry::WasmerConfig::from_file(&wasmer_dir)
            .map_err(|e| anyhow::anyhow!("could not load config {e}"))?;
        let token_opt = config
            .registry
            .tokens
            .iter()
            .find(|t| t.registry == registry)
            .map(|x| x.token.clone());
        Ok(token_opt)
    }

    fn token(&self) -> Result<Option<String>, anyhow::Error> {
        if let Some(token) = &self.token {
            Ok(Some(token.clone()))
        } else {
            let registry = self
                .registry
                .as_ref()
                .map(|x| x.as_str())
                .unwrap_or(Self::DEV_REGISTRY);
            Self::user_token_from_config(registry)
        }
    }

    fn client(&self) -> Result<wasmer_api::backend::BackendClient, anyhow::Error> {
        let registry = self
            .registry
            .clone()
            .unwrap_or_else(|| Self::DEV_REGISTRY.parse().unwrap());
        let client = wasmer_api::backend::BackendClient::new(registry);

        let client = if let Some(token) = self.token()? {
            client.with_auth_token(token)
        } else {
            client
        };

        Ok(client)
    }
}

/// Formatting options for a single item.
#[derive(clap::Parser, Debug)]
pub struct ItemFormatOpts {
    /// Output format. (json, text)
    #[clap(short = 'f', long, default_value = "yaml")]
    pub format: util::render::ItemFormat,
}

/// Formatting options for a list of items.
#[derive(clap::Parser, Debug)]
pub struct ListFormatOpts {
    /// Output format. (json, text)
    #[clap(short = 'f', long, default_value = "table")]
    pub format: util::render::ListFormat,
}

/// Initialize logging.
///
/// This will prefer the `$RUST_LOG` environment variable, with the `-v` and
/// `-q` flags being used to modify the default log level.
///
/// For example, running `RUST_LOG=wasmer_registry=debug wasmer-deploy -q` will
/// log everything at the `error` level (`-q` means to be one level more quiet
/// than the default `warn`), but anything from the `wasmer_registry` crate will
/// be logged at the `debug` level.
fn initialize_logging(args: &Args) {
    let level = args.verbosity.log_level_filter();

    let fmt_layer = fmt::layer()
        .with_target(true)
        .with_span_events(fmt::format::FmtSpan::CLOSE)
        .with_writer(std::io::stderr)
        .compact();

    let default_level = match level {
        log::LevelFilter::Off => tracing::level_filters::LevelFilter::OFF,
        log::LevelFilter::Error => tracing::level_filters::LevelFilter::ERROR,
        log::LevelFilter::Warn => tracing::level_filters::LevelFilter::WARN,
        log::LevelFilter::Info => tracing::level_filters::LevelFilter::INFO,
        log::LevelFilter::Debug => tracing::level_filters::LevelFilter::DEBUG,
        log::LevelFilter::Trace => tracing::level_filters::LevelFilter::TRACE,
    };

    let filter_layer = EnvFilter::builder()
        .with_default_directive(default_level.into())
        .from_env_lossy();

    tracing_subscriber::registry()
        .with(filter_layer)
        .with(fmt_layer)
        .init();
}