From a6f6bfa7b07ad7879b8839ed8d90a3491048407e Mon Sep 17 00:00:00 2001 From: petarjuki7 <36903459+petarjuki7@users.noreply.github.com> Date: Fri, 15 Nov 2024 09:14:34 +0100 Subject: [PATCH] Added update command (#960) --- crates/meroctl/src/cli/context.rs | 4 + crates/meroctl/src/cli/context/update.rs | 257 +++++++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 crates/meroctl/src/cli/context/update.rs diff --git a/crates/meroctl/src/cli/context.rs b/crates/meroctl/src/cli/context.rs index 42ebfde65..47c68c49c 100644 --- a/crates/meroctl/src/cli/context.rs +++ b/crates/meroctl/src/cli/context.rs @@ -9,6 +9,7 @@ use crate::cli::context::get::GetCommand; use crate::cli::context::invite::InviteCommand; use crate::cli::context::join::JoinCommand; use crate::cli::context::list::ListCommand; +use crate::cli::context::update::UpdateCommand; use crate::cli::context::watch::WatchCommand; use crate::cli::Environment; use crate::output::Report; @@ -19,6 +20,7 @@ mod get; mod invite; mod join; mod list; +mod update; mod watch; pub const EXAMPLES: &str = r" @@ -55,6 +57,7 @@ pub enum ContextSubCommands { Delete(DeleteCommand), #[command(alias = "ws")] Watch(WatchCommand), + Update(UpdateCommand), } impl Report for Context { @@ -75,6 +78,7 @@ impl ContextCommand { ContextSubCommands::Join(join) => join.run(environment).await, ContextSubCommands::List(list) => list.run(environment).await, ContextSubCommands::Watch(watch) => watch.run(environment).await, + ContextSubCommands::Update(update) => update.run(environment).await, } } } diff --git a/crates/meroctl/src/cli/context/update.rs b/crates/meroctl/src/cli/context/update.rs new file mode 100644 index 000000000..ec37ed6dc --- /dev/null +++ b/crates/meroctl/src/cli/context/update.rs @@ -0,0 +1,257 @@ +use calimero_primitives::application::ApplicationId; +use calimero_primitives::context::ContextId; +use calimero_primitives::identity::PublicKey; +use calimero_server_primitives::admin::{ + InstallApplicationResponse, InstallDevApplicationRequest, UpdateContextApplicationRequest, + UpdateContextApplicationResponse, +}; +use camino::Utf8PathBuf; +use clap::Parser; +use eyre::{bail, Result as EyreResult}; +use libp2p::identity::Keypair; +use libp2p::Multiaddr; +use notify::event::ModifyKind; +use notify::{EventKind, RecursiveMode, Watcher}; +use reqwest::Client; +use tokio::runtime::Handle; +use tokio::sync::mpsc; + +use crate::cli::Environment; +use crate::common::{do_request, fetch_multiaddr, load_config, multiaddr_to_url, RequestType}; +use crate::output::{ErrorLine, InfoLine}; + +#[derive(Debug, Parser)] +#[command(about = "Update app in context")] +pub struct UpdateCommand { + #[clap(long, short = 'c', help = "ContextId where to install the application")] + context_id: ContextId, + + #[clap( + long, + short = 'a', + help = "The application ID to update in the context" + )] + application_id: Option, + + #[clap( + long, + conflicts_with = "application_id", + help = "Path to the application file to watch and install locally" + )] + path: Option, + + #[clap( + long, + conflicts_with = "application_id", + help = "Metadata needed for the application installation" + )] + metadata: Option, + + #[clap( + long, + short = 'w', + conflicts_with = "application_id", + requires = "path" + )] + watch: bool, + + #[arg(long = "as", help = "Public key of the executor")] + pub executor: PublicKey, +} + +impl UpdateCommand { + pub async fn run(self, environment: &Environment) -> EyreResult<()> { + let config = load_config(&environment.args.home, &environment.args.node_name)?; + let multiaddr = fetch_multiaddr(&config)?; + let client = Client::new(); + + match self { + Self { + context_id, + application_id: Some(application_id), + path: None, + metadata: None, + watch: false, + executor: executor_public_key, + } => { + update_context_application( + environment, + &client, + multiaddr, + context_id, + application_id, + &config.identity, + executor_public_key, + ) + .await?; + } + Self { + context_id, + application_id: None, + path: Some(path), + metadata, + executor: executor_public_key, + .. + } => { + let metadata = metadata.map(String::into_bytes); + + let application_id = install_app( + environment, + &client, + multiaddr, + path.clone(), + metadata.clone(), + &config.identity, + ) + .await?; + + update_context_application( + environment, + &client, + multiaddr, + context_id, + application_id, + &config.identity, + executor_public_key, + ) + .await?; + + if self.watch { + watch_app_and_update_context( + environment, + &client, + multiaddr, + context_id, + path, + metadata, + &config.identity, + executor_public_key, + ) + .await?; + } + } + + _ => bail!("Invalid command configuration"), + } + + Ok(()) + } +} + +async fn install_app( + environment: &Environment, + client: &Client, + base_multiaddr: &Multiaddr, + path: Utf8PathBuf, + metadata: Option>, + keypair: &Keypair, +) -> EyreResult { + let url = multiaddr_to_url(base_multiaddr, "admin-api/dev/install-dev-application")?; + + let request = InstallDevApplicationRequest::new(path, metadata.unwrap_or_default()); + + let response: InstallApplicationResponse = + do_request(client, url, Some(request), keypair, RequestType::Post).await?; + + environment.output.write(&response); + + Ok(response.data.application_id) +} + +async fn update_context_application( + environment: &Environment, + client: &Client, + base_multiaddr: &Multiaddr, + context_id: ContextId, + application_id: ApplicationId, + keypair: &Keypair, + member_public_key: PublicKey, +) -> EyreResult<()> { + let url = multiaddr_to_url( + base_multiaddr, + &format!("admin-api/dev/contexts/{context_id}/application"), + )?; + + let request = UpdateContextApplicationRequest::new(application_id, member_public_key); + + let response: UpdateContextApplicationResponse = + do_request(client, url, Some(request), keypair, RequestType::Post).await?; + + environment.output.write(&response); + + Ok(()) +} + +async fn watch_app_and_update_context( + environment: &Environment, + client: &Client, + base_multiaddr: &Multiaddr, + context_id: ContextId, + path: Utf8PathBuf, + metadata: Option>, + keypair: &Keypair, + member_public_key: PublicKey, +) -> EyreResult<()> { + let (tx, mut rx) = mpsc::channel(1); + + let handle = Handle::current(); + let mut watcher = notify::recommended_watcher(move |evt| { + handle.block_on(async { + drop(tx.send(evt).await); + }); + })?; + + watcher.watch(path.as_std_path(), RecursiveMode::NonRecursive)?; + + environment + .output + .write(&InfoLine(&format!("Watching for changes to {path}"))); + + while let Some(event) = rx.recv().await { + let event = match event { + Ok(event) => event, + Err(err) => { + environment.output.write(&ErrorLine(&format!("{err:?}"))); + continue; + } + }; + + match event.kind { + EventKind::Modify(ModifyKind::Data(_)) => {} + EventKind::Remove(_) => { + environment + .output + .write(&ErrorLine("File removed, ignoring..")); + continue; + } + EventKind::Any + | EventKind::Access(_) + | EventKind::Create(_) + | EventKind::Modify(_) + | EventKind::Other => continue, + } + + let application_id = install_app( + environment, + client, + base_multiaddr, + path.clone(), + metadata.clone(), + keypair, + ) + .await?; + + update_context_application( + environment, + client, + base_multiaddr, + context_id, + application_id, + keypair, + member_public_key, + ) + .await?; + } + + Ok(()) +}