diff --git a/Cargo.lock b/Cargo.lock index 6a50894..a13f5d3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1110,7 +1110,7 @@ dependencies = [ [[package]] name = "detee-shared" version = "0.1.0" -source = "git+ssh://git@gitea.detee.cloud/testnet/proto.git?branch=main#70e83dd0e982eeb491212c4a9d265df0b148fe24" +source = "git+ssh://git@gitea.detee.cloud/testnet/proto.git?branch=main#3024c00b8e1c93e70902793385b92bc0a8d1f26a" dependencies = [ "base64", "prost", diff --git a/src/bin/detee-cli.rs b/src/bin/detee-cli.rs index 33e3fd3..ecb5a29 100644 --- a/src/bin/detee-cli.rs +++ b/src/bin/detee-cli.rs @@ -204,6 +204,19 @@ fn clap_cmd() -> Command { .action(clap::ArgAction::Append) ) ) + .subcommand(Command::new("inspect").about("list all available information about a App") + .arg( + Arg::new("uuid") + .help("supply the uuid of the App contract") + .required(true) + ) + .arg( + Arg::new("show-node") + .long("show-node") + .help("inspect the node that is runnnig this App") + .action(clap::ArgAction::SetTrue) + ) + ) .subcommand( Command::new("delete") .about("delete deployed app") diff --git a/src/sgx/cli_handler.rs b/src/sgx/cli_handler.rs index dd1a77c..ce62845 100644 --- a/src/sgx/cli_handler.rs +++ b/src/sgx/cli_handler.rs @@ -1,18 +1,21 @@ -use super::grpc_brain::list_apps; -use super::utils::deploy_new_app_and_update_config; -use super::{get_app_node, AppContract, AppDeployResponse}; +use crate::sgx::utils::deploy_new_app_and_update_config; +use crate::config::Config; use crate::name_generator::random_app_name; -use crate::sgx; use crate::sgx::config::{validate_yaml, DeteeCliExt}; -use crate::sgx::grpc_brain::delete_app; +use crate::sgx::grpc_brain::{delete_app, list_contracts}; use crate::sgx::grpc_dtpm::{attest_and_send_config, get_config_from_enclave}; use crate::sgx::packaging::package_enclave; use crate::sgx::utils::{fetch_config_and_mr_enclave, override_envs_and_args_launch_config}; use crate::sgx::AppDeleteResponse; +use crate::sgx::{ + get_app_node, get_app_node_by_contract, get_one_contract, inspect_node, print_nodes, +}; +use crate::sgx::{AppContract, AppDeployResponse}; use crate::utils::block_on; use crate::{cli_print, SimpleOutput}; use clap::ArgMatches; +use detee_shared::app_proto::ListAppContractsReq; use detee_shared::sgx::types::brain::AppDeployConfig; use detee_shared::sgx::types::brain::Resource; @@ -20,8 +23,9 @@ pub fn handle_app(app_matche: &ArgMatches) { match app_matche.subcommand() { Some(("package", subcom_args)) => cli_print(handle_package(subcom_args)), Some(("deploy", subcom_args)) => cli_print(handle_deploy(subcom_args)), + Some(("inspect", subcom_args)) => handle_inspect(subcom_args), Some(("delete", subcom_args)) => cli_print(handle_delete(subcom_args)), - Some(("list", subcom_args)) => cli_print(handle_list(subcom_args)), + Some(("list", _)) => cli_print(handle_list()), Some(("config", subcom_args)) => handle_config(subcom_args), _ => println!("No valid subcommand provided. Use --help for more information."), } @@ -29,10 +33,10 @@ pub fn handle_app(app_matche: &ArgMatches) { pub fn handle_app_nodes(matches: &ArgMatches) { match matches.subcommand() { - Some(("search", _)) => cli_print(sgx::print_nodes().map_err(Into::into)), + Some(("search", _)) => cli_print(print_nodes().map_err(Into::into)), Some(("inspect", subcom_args)) => { let ip: String = subcom_args.get_one::("ip").unwrap().clone(); - cli_print(sgx::inspect_node(ip).map_err(Into::into)); + cli_print(inspect_node(ip).map_err(Into::into)); } Some(("report", _)) => { // let node_pubkey: String = path_subcommand.get_one::("pubkey").unwrap().clone(); @@ -130,6 +134,15 @@ fn handle_deploy( } } +fn handle_inspect(inspect_match: &ArgMatches) { + let uuid: String = inspect_match.get_one::("uuid").unwrap().clone(); + if *inspect_match.get_one::("show-node").unwrap() { + cli_print(block_on(get_app_node_by_contract(&uuid)).map_err(Into::into)); + } else { + cli_print(block_on(get_one_contract(&uuid)).map_err(Into::into)) + } +} + fn handle_delete( delete_match: &ArgMatches, ) -> Result> { @@ -150,13 +163,13 @@ fn handle_delete( } } -fn handle_list(_: &ArgMatches) -> Result, Box> { - match block_on(list_apps()) { - Ok(app_contracts) => { - Ok(app_contracts.into_iter().map(AppContract::from).collect::>()) - } - Err(e) => Err(Box::new(e)), - } +fn handle_list() -> Result, Box> { + let req = + ListAppContractsReq { admin_pubkey: Config::get_detee_wallet()?, ..Default::default() }; + let contracts: Vec = + block_on(list_contracts(req))?.into_iter().map(|n| n.into()).collect(); + + Ok(contracts) } fn handle_config(matches: &ArgMatches) { diff --git a/src/sgx/grpc_brain.rs b/src/sgx/grpc_brain.rs index 3d458c3..8bc7324 100644 --- a/src/sgx/grpc_brain.rs +++ b/src/sgx/grpc_brain.rs @@ -36,6 +36,30 @@ impl crate::HumanOutput for AppNodeListResp { } } +impl crate::HumanOutput for AppContract { + fn human_cli_print(&self) { + let app_resource = self.resource.clone().unwrap_or_default(); + let mapped_ports = self + .mapped_ports + .clone() + .iter() + .map(|p| format!("({},{})", p.host_port, p.app_port)) + .collect::>() + .join(", "); + println!( + "The App {} has the UUID {}, and it runs on the node {}", + self.app_name, self.uuid, self.node_pubkey + ); + println!("The app has mapped ports by the node are: {mapped_ports}"); + println!( + "The App has {} vCPUS, {}MB of memory and a disk of {} MB.", + app_resource.vcpu, app_resource.memory_mb, app_resource.disk_mb + ); + println!("You have locked {} nanoLP in the contract, that get collected at a rate of {} nanoLP per minute.", + self.locked_nano, self.nano_per_minute); + } +} + pub async fn new_app(app_deploy_config: AppDeployConfig) -> Result { let resource = app_deploy_config.clone().resource; let mut req: NewAppReq = app_deploy_config.clone().into(); @@ -66,12 +90,9 @@ pub async fn delete_app(app_uuid: String) -> Result<()> { Ok(()) } -pub async fn list_apps() -> Result> { - let admin_pubkey = Config::get_detee_wallet().unwrap(); - let list_req = ListAppContractsReq { admin_pubkey }; +pub async fn list_contracts(req: ListAppContractsReq) -> Result> { let mut daemon_serivce = BrainAppCliClient::connect(Config::get_brain_url()).await?; - let mut res_stream = - daemon_serivce.list_app_contracts(sign_request(list_req)?).await?.into_inner(); + let mut res_stream = daemon_serivce.list_app_contracts(sign_request(req)?).await?.into_inner(); let mut app_contracts = vec![]; diff --git a/src/sgx/mod.rs b/src/sgx/mod.rs index 19a0f20..22dbd3d 100644 --- a/src/sgx/mod.rs +++ b/src/sgx/mod.rs @@ -5,11 +5,13 @@ pub mod grpc_dtpm; pub mod packaging; pub mod utils; +use crate::config::Config; use crate::snp; use crate::{constants::HRATLS_APP_PORT, utils::block_on}; use detee_shared::{ app_proto::{ - AppContract as AppContractPB, AppNodeFilters, AppNodeListResp, AppResource, NewAppRes, + AppContract as AppContractPB, AppNodeFilters, AppNodeListResp, AppResource, + ListAppContractsReq, NewAppRes, }, sgx::types::brain::Resource, }; @@ -17,6 +19,16 @@ use grpc_brain::get_one_app_node; use serde::{Deserialize, Serialize}; use tabled::Tabled; +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error(transparent)] + Config(#[from] crate::config::Error), + #[error("Could not find a contract with the ID {0}")] + AppContractNotFound(String), + #[error("Brain returned the following error: {0}")] + Brain(#[from] grpc_brain::Error), +} + #[derive(Tabled, Debug, Serialize, Deserialize)] pub struct AppContract { #[tabled(rename = "Location")] @@ -101,6 +113,21 @@ impl From for AppContract { } } +pub async fn get_one_contract(uuid: &str) -> Result { + let req = ListAppContractsReq { + admin_pubkey: Config::get_detee_wallet()?, + uuid: uuid.to_string(), + ..Default::default() + }; + let contracts = grpc_brain::list_contracts(req).await?; + + if contracts.is_empty() { + return Err(Error::AppContractNotFound(uuid.to_string())); + } + // let _ = write_uuid_list(&contracts); + Ok(contracts[0].clone()) +} + #[derive(Debug, Serialize, Deserialize)] pub struct AppDeployResponse { pub status: String, @@ -149,7 +176,6 @@ impl crate::HumanOutput for AppDeleteResponse { pub async fn get_app_node( resource: Resource, - // TODO: change this to utils or something location: snp::deploy::Location, ) -> Result { let app_node_filter = AppNodeFilters { @@ -211,3 +237,9 @@ pub fn print_nodes() -> Result, grpc_brain::Error> { let req = AppNodeFilters { ..Default::default() }; block_on(grpc_brain::get_app_node_list(req)) } + +pub async fn get_app_node_by_contract(uuid: &str) -> Result { + let contract = get_one_contract(uuid).await?; + Ok(get_one_app_node(AppNodeFilters { node_pubkey: contract.node_pubkey, ..Default::default() }) + .await?) +} diff --git a/src/sgx/utils.rs b/src/sgx/utils.rs index b78aa38..6bdbd9d 100644 --- a/src/sgx/utils.rs +++ b/src/sgx/utils.rs @@ -1,6 +1,6 @@ use super::grpc_brain::new_app; use crate::constants::HRATLS_APP_PORT; -use crate::sgx::grpc_brain::list_apps; +use crate::sgx::get_one_contract; use crate::sgx::grpc_dtpm::attest_and_send_config; use detee_shared::app_proto::NewAppRes; use detee_shared::sgx::types::brain::AppDeployConfig; @@ -27,9 +27,8 @@ pub enum Error { } pub async fn hratls_url_and_mr_enclave_from_app_id(app_id: &str) -> (String, Option<[u8; 32]>) { - let app_contracts = list_apps().await.expect("Could not get App contracts"); - let app_contract = app_contracts.iter().find(|contract| contract.uuid == app_id); - if app_contract.is_none() { + let app_contract = get_one_contract(app_id).await; + if app_contract.is_err() { eprintln!("Could not find App contract with ID: {}", app_id); std::process::exit(1); } @@ -98,6 +97,7 @@ pub fn calculate_nanolp_for_app( let total_units = (vcpus as f64 * 5f64) + (memory_mb as f64 / 200f64) + (disk_size_mb as f64 / 10000f64); let locked_nano = (hours as f64 * 60f64 * total_units * node_price as f64) as u64; + // TODO: change all println to eprintln println!( "Node price: {}/unit/minute. Total Units for hardware requested: {:.4}. Locking {} LP (offering the App for {} hours).", node_price as f64 / 1_000_000_000.0,