use std::{collections::HashMap, path::PathBuf};
use anyhow::{bail, Result};
use clap::{Args, Subcommand};
use serde_json::json;
use wadm::server::{
DeleteModelResponse, DeployModelResponse, GetModelResponse, GetResult, ModelSummary,
PutModelResponse, PutResult, VersionResponse,
};
use wash_lib::{
cli::{CliConnectionOpts, CommandOutput, OutputKind},
config::WashConnectionOptions,
};
use crate::appearance::spinner::Spinner;
mod output;
#[derive(Debug, Clone, Subcommand)]
pub(crate) enum AppCliCommand {
#[clap(name = "list")]
List(ListCommand),
#[clap(name = "get")]
Get(GetCommand),
#[clap(name = "history")]
History(HistoryCommand),
#[clap(name = "del")]
Delete(DeleteCommand),
#[clap(name = "put")]
Put(PutCommand),
#[clap(name = "deploy")]
Deploy(DeployCommand),
#[clap(name = "undeploy")]
Undeploy(UndeployCommand),
}
#[derive(Args, Debug, Clone)]
pub(crate) struct ListCommand {
#[clap(flatten)]
opts: CliConnectionOpts,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct UndeployCommand {
#[clap(name = "name")]
model_name: String,
#[clap(long = "non-destructive")]
non_destructive: bool,
#[clap(flatten)]
opts: CliConnectionOpts,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct DeployCommand {
#[clap(name = "name")]
model_name: String,
#[clap(name = "version")]
version: Option<String>,
#[clap(flatten)]
opts: CliConnectionOpts,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct DeleteCommand {
#[clap(name = "name")]
model_name: String,
#[clap(long = "delete-all")]
delete_all: bool,
#[clap(name = "version", required_unless_present("delete_all"))]
version: Option<String>,
#[clap(flatten)]
opts: CliConnectionOpts,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct PutCommand {
source: PathBuf,
#[clap(flatten)]
opts: CliConnectionOpts,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct GetCommand {
#[clap(name = "name")]
model_name: String,
#[clap(name = "version")]
version: Option<String>,
#[clap(flatten)]
opts: CliConnectionOpts,
}
#[derive(Args, Debug, Clone)]
pub(crate) struct HistoryCommand {
#[clap(name = "name")]
model_name: String,
#[clap(flatten)]
opts: CliConnectionOpts,
}
pub(crate) async fn handle_command(
command: AppCliCommand,
output_kind: OutputKind,
) -> Result<CommandOutput> {
use AppCliCommand::*;
let sp: Spinner = Spinner::new(&output_kind)?;
let out: CommandOutput = match command {
List(cmd) => {
sp.update_spinner_message("Querying app spec list ...".to_string());
let results = get_models(cmd).await?;
list_models_output(results)
}
Get(cmd) => {
sp.update_spinner_message("Querying app spec details ... ".to_string());
let results = get_model_details(cmd).await?;
show_model_output(results)
}
History(cmd) => {
sp.update_spinner_message("Querying app revision history ... ".to_string());
let results = get_model_history(cmd).await?;
show_model_history(results)
}
Delete(cmd) => {
sp.update_spinner_message("Deleting app version ... ".to_string());
let results = delete_model_version(cmd).await?;
show_del_results(results)
}
Put(cmd) => {
sp.update_spinner_message("Uploading app specification ... ".to_string());
let results = put_model(cmd).await?;
show_put_results(results)
}
Deploy(cmd) => {
sp.update_spinner_message("Deploying application ... ".to_string());
let results = deploy_model(cmd).await?;
show_deploy_results(results)
}
Undeploy(cmd) => {
sp.update_spinner_message("Undeploying application ... ".to_string());
let results = undeploy_model(cmd).await?;
show_undeploy_results(results)
}
};
sp.finish_and_clear();
Ok(out)
}
async fn undeploy_model(cmd: UndeployCommand) -> Result<DeployModelResponse> {
let lattice_prefix = cmd.opts.lattice_prefix.clone();
let client = <CliConnectionOpts as TryInto<WashConnectionOptions>>::try_into(cmd.opts)?
.into_nats_client()
.await?;
wash_lib::app::undeploy_model(
&client,
lattice_prefix,
&cmd.model_name,
cmd.non_destructive,
)
.await
}
async fn deploy_model(cmd: DeployCommand) -> Result<DeployModelResponse> {
let lattice_prefix = cmd.opts.lattice_prefix.clone();
let client = <CliConnectionOpts as TryInto<WashConnectionOptions>>::try_into(cmd.opts)?
.into_nats_client()
.await?;
let model_name = if tokio::fs::metadata(&cmd.model_name).await.is_ok() {
let put_res = wash_lib::app::put_model(
&client,
lattice_prefix.clone(),
&tokio::fs::read_to_string(&cmd.model_name).await?,
)
.await?;
match put_res.result {
PutResult::Created | PutResult::NewVersion => put_res.name,
_ => bail!("Could not put manifest to deploy {}", put_res.message),
}
} else {
cmd.model_name
};
wash_lib::app::deploy_model(&client, lattice_prefix, &model_name, cmd.version).await
}
async fn put_model(cmd: PutCommand) -> Result<PutModelResponse> {
let lattice_prefix = cmd.opts.lattice_prefix.clone();
let client = <CliConnectionOpts as TryInto<WashConnectionOptions>>::try_into(cmd.opts)?
.into_nats_client()
.await?;
wash_lib::app::put_model(
&client,
lattice_prefix,
&tokio::fs::read_to_string(&cmd.source).await?,
)
.await
}
async fn get_model_history(cmd: HistoryCommand) -> Result<VersionResponse> {
let lattice_prefix = cmd.opts.lattice_prefix.clone();
let client = <CliConnectionOpts as TryInto<WashConnectionOptions>>::try_into(cmd.opts)?
.into_nats_client()
.await?;
wash_lib::app::get_model_history(&client, lattice_prefix, &cmd.model_name).await
}
async fn get_model_details(cmd: GetCommand) -> Result<GetModelResponse> {
let lattice_prefix = cmd.opts.lattice_prefix.clone();
let client = <CliConnectionOpts as TryInto<WashConnectionOptions>>::try_into(cmd.opts)?
.into_nats_client()
.await?;
wash_lib::app::get_model_details(&client, lattice_prefix, &cmd.model_name, cmd.version).await
}
async fn delete_model_version(cmd: DeleteCommand) -> Result<DeleteModelResponse> {
let lattice_prefix = cmd.opts.lattice_prefix.clone();
let client = <CliConnectionOpts as TryInto<WashConnectionOptions>>::try_into(cmd.opts)?
.into_nats_client()
.await?;
wash_lib::app::delete_model_version(
&client,
lattice_prefix,
&cmd.model_name,
cmd.version,
cmd.delete_all,
)
.await
}
async fn get_models(cmd: ListCommand) -> Result<Vec<ModelSummary>> {
let lattice_prefix = cmd.opts.lattice_prefix.clone();
let client = <CliConnectionOpts as TryInto<WashConnectionOptions>>::try_into(cmd.opts)?
.into_nats_client()
.await?;
wash_lib::app::get_models(&client, lattice_prefix).await
}
fn list_models_output(results: Vec<ModelSummary>) -> CommandOutput {
let mut map = HashMap::new();
map.insert("apps".to_string(), json!(results));
CommandOutput::new(output::list_models_table(results), map)
}
fn show_model_output(md: GetModelResponse) -> CommandOutput {
let mut map = HashMap::new();
map.insert("model".to_string(), json!(md));
if md.result == GetResult::Success {
let yaml = serde_yaml::to_string(&md.manifest).unwrap();
CommandOutput::new(yaml, map)
} else {
CommandOutput::new(md.message, map)
}
}
fn show_put_results(results: PutModelResponse) -> CommandOutput {
let mut map = HashMap::new();
map.insert("results".to_string(), json!(results));
CommandOutput::new(results.message, map)
}
fn show_undeploy_results(results: DeployModelResponse) -> CommandOutput {
let mut map = HashMap::new();
map.insert("results".to_string(), json!(results));
CommandOutput::new(results.message, map)
}
fn show_del_results(results: DeleteModelResponse) -> CommandOutput {
let mut map = HashMap::new();
map.insert("deleted".to_string(), json!(results));
CommandOutput::new(results.message, map)
}
fn show_deploy_results(results: DeployModelResponse) -> CommandOutput {
let mut map = HashMap::new();
map.insert("acknowledged".to_string(), json!(results));
CommandOutput::new(results.message, map)
}
fn show_model_history(results: VersionResponse) -> CommandOutput {
let mut map = HashMap::new();
map.insert("revisions".to_string(), json!(results));
CommandOutput::new(output::list_revisions_table(results.versions), map)
}