From d1f43838c7d9ebb814021325fd02366fb40d2c46 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 2 Oct 2023 21:35:22 +0300 Subject: [PATCH 1/3] feat(core): add a method to list plugins in a remote repoistory Signed-off-by: Tarek --- coffee_cmd/src/cmd.rs | 20 +++++- coffee_cmd/src/main.rs | 91 +++++++++++++++++++-------- coffee_core/src/coffee.rs | 10 +++ coffee_core/src/lib.rs | 2 +- coffee_lib/src/plugin_manager.rs | 3 + coffee_lib/src/types/mod.rs | 10 +++ tests/src/coffee_integration_tests.rs | 31 +++++++-- 7 files changed, 132 insertions(+), 35 deletions(-) diff --git a/coffee_cmd/src/cmd.rs b/coffee_cmd/src/cmd.rs index 0025d671..438fb06d 100644 --- a/coffee_cmd/src/cmd.rs +++ b/coffee_cmd/src/cmd.rs @@ -41,7 +41,11 @@ pub enum CoffeeCommand { #[clap(arg_required_else_help = true)] Remote { #[clap(subcommand)] - action: RemoteAction, + action: Option, + #[arg(short, long, action = clap::ArgAction::SetTrue, requires = "remote-name")] + plugins: bool, + #[arg(name = "remote-name", help = "The name of the remote repository")] + name: Option, }, /// Configure coffee with the core lightning /// configuration @@ -60,8 +64,11 @@ pub enum CoffeeCommand { #[derive(Debug, Subcommand)] pub enum RemoteAction { + /// Add a remote repository to the plugin manager. Add { name: String, url: String }, + /// Remove a remote repository from the plugin manager. Rm { name: String }, + /// List the remote repositories from the plugin manager. List {}, } @@ -76,7 +83,16 @@ impl From<&CoffeeCommand> for coffee_core::CoffeeOperation { CoffeeCommand::Upgrade { repo } => Self::Upgrade(repo.to_owned()), CoffeeCommand::List {} => Self::List, CoffeeCommand::Setup { cln_conf } => Self::Setup(cln_conf.to_owned()), - CoffeeCommand::Remote { action } => Self::Remote(action.into()), + CoffeeCommand::Remote { + action, + plugins, + name, + } => { + if let Some(action) = action { + return Self::Remote(Some(action.into()), *plugins, name.clone()); + } + Self::Remote(None, *plugins, name.clone()) + } CoffeeCommand::Remove { plugin } => Self::Remove(plugin.to_owned()), CoffeeCommand::Show { plugin } => Self::Show(plugin.to_owned()), CoffeeCommand::Search { plugin } => Self::Search(plugin.to_owned()), diff --git a/coffee_cmd/src/main.rs b/coffee_cmd/src/main.rs index ed37abf9..1ae25355 100644 --- a/coffee_cmd/src/main.rs +++ b/coffee_cmd/src/main.rs @@ -5,9 +5,10 @@ use clap::Parser; use radicle_term as term; use coffee_core::coffee::CoffeeManager; +use coffee_lib::error; use coffee_lib::errors::CoffeeError; use coffee_lib::plugin_manager::PluginManager; -use coffee_lib::types::response::UpgradeStatus; +use coffee_lib::types::response::{CoffeeRemote, UpgradeStatus}; use crate::cmd::CoffeeArgs; use crate::cmd::CoffeeCommand; @@ -73,34 +74,70 @@ async fn main() -> Result<(), CoffeeError> { } Ok(()) } - CoffeeCommand::Remote { action } => match action { - RemoteAction::Add { name, url } => { - let mut spinner = term::spinner(format!("Fetch remote from {url}")); - let result = coffee.add_remote(&name, &url).await; - if let Err(err) = &result { - spinner.error(format!("Error while add remote: {err}")); - return result; - } - spinner.message("Remote added!"); - spinner.finish(); - Ok(()) - } - RemoteAction::Rm { name } => { - let mut spinner = term::spinner(format!("Removing remote {name}")); - let result = coffee.rm_remote(&name).await; - if let Err(err) = &result { - spinner.error(format!("Error while removing the repository: {err}")); - return result; + CoffeeCommand::Remote { + action, + plugins, + name, + } => { + if plugins { + let result = coffee.get_plugins_in_remote(&name.unwrap()).await; + coffee_term::show_list(result) + } else { + match action { + Some(RemoteAction::Add { name, url }) => { + let mut spinner = term::spinner(format!("Fetch remote from {url}")); + let result = coffee.add_remote(&name, &url).await; + if let Err(err) = &result { + spinner.error(format!("Error while add remote: {err}")); + return result; + } + spinner.message("Remote added!"); + spinner.finish(); + Ok(()) + } + Some(RemoteAction::Rm { name }) => { + let mut spinner = term::spinner(format!("Removing remote {name}")); + let result = coffee.rm_remote(&name).await; + if let Err(err) = &result { + spinner.error(format!("Error while removing the repository: {err}")); + return result; + } + spinner.message("Remote removed!"); + spinner.finish(); + Ok(()) + } + Some(RemoteAction::List {}) => { + let remotes = coffee.list_remotes().await; + coffee_term::show_remote_list(remotes) + } + None => { + // This is the case when the user does not provides the + // plugins flag, so we just show the remote repository + // information + + // The name will be always Some because of the + // arg_required_else_help = true in the clap + // attribute + let name = + name.ok_or_else(|| error!("No remote repository name provided"))?; + let remotes = coffee.list_remotes().await?; + let remotes = remotes + .remotes + .ok_or_else(|| error!("Couldn't get the remote repositories"))?; + let remote = remotes + .iter() + .find(|remote| remote.local_name == name) + .ok_or_else(|| error!("Couldn't find the remote repository"))?; + // A workaround to show the remote repository information + // in the same way as the list command + let remote = Ok(CoffeeRemote { + remotes: Some(vec![remote.clone()]), + }); + coffee_term::show_remote_list(remote) + } } - spinner.message("Remote removed!"); - spinner.finish(); - Ok(()) } - RemoteAction::List {} => { - let remotes = coffee.list_remotes().await; - coffee_term::show_remote_list(remotes) - } - }, + } CoffeeCommand::Setup { cln_conf } => { // FIXME: read the core lightning config // and the coffee script diff --git a/coffee_core/src/coffee.rs b/coffee_core/src/coffee.rs index 651ba58f..66b29e2c 100644 --- a/coffee_core/src/coffee.rs +++ b/coffee_core/src/coffee.rs @@ -414,6 +414,16 @@ impl PluginManager for CoffeeManager { }) } + async fn get_plugins_in_remote(&self, name: &str) -> Result { + log::debug!("Listing plugins for repository: {}", name); + let repo = self + .repos + .get(name) + .ok_or_else(|| error!("repository with name: {name} not found"))?; + let plugins = repo.list().await?; + Ok(CoffeeList { plugins }) + } + async fn show(&mut self, plugin: &str) -> Result { for repo in self.repos.values() { if let Some(plugin) = repo.get_plugin_by_name(plugin) { diff --git a/coffee_core/src/lib.rs b/coffee_core/src/lib.rs index 639a615e..6e5588f1 100644 --- a/coffee_core/src/lib.rs +++ b/coffee_core/src/lib.rs @@ -15,7 +15,7 @@ pub enum CoffeeOperation { Upgrade(String), Remove(String), /// Remote(name repository, url of the repository) - Remote(RemoteAction), + Remote(Option, bool, Option), /// Setup(core lightning root path) Setup(String), Show(String), diff --git a/coffee_lib/src/plugin_manager.rs b/coffee_lib/src/plugin_manager.rs index e900d69f..9d42626e 100644 --- a/coffee_lib/src/plugin_manager.rs +++ b/coffee_lib/src/plugin_manager.rs @@ -36,6 +36,9 @@ pub trait PluginManager { /// list the remote repositories for the plugin manager. async fn list_remotes(&mut self) -> Result; + /// List the plugins available in a remote repository. + async fn get_plugins_in_remote(&self, name: &str) -> Result; + /// set up the core lightning configuration target for the /// plugin manager. async fn setup(&mut self, cln_conf_path: &str) -> Result<(), CoffeeError>; diff --git a/coffee_lib/src/types/mod.rs b/coffee_lib/src/types/mod.rs index f946eb64..5ff63f36 100644 --- a/coffee_lib/src/types/mod.rs +++ b/coffee_lib/src/types/mod.rs @@ -70,6 +70,12 @@ pub mod request { pub repository_name: String, } + #[cfg(feature = "open-api")] + #[derive(Debug, Deserialize, Apiv2Schema, Serialize)] + pub struct RemotePluginsList { + pub repository_name: String, + } + #[cfg(feature = "open-api")] #[derive(Debug, Deserialize, Apiv2Schema, Serialize)] pub struct Show { @@ -96,6 +102,10 @@ pub mod response { pub plugin: Plugin, } + // This struct is used to represent the list of plugins + // that are installed in the coffee configuration + // or the list of plugins that are available in a remote + // repository. #[derive(Debug, Serialize, Deserialize)] pub struct CoffeeList { pub plugins: Vec, diff --git a/tests/src/coffee_integration_tests.rs b/tests/src/coffee_integration_tests.rs index 5f48b332..3d7c6b41 100644 --- a/tests/src/coffee_integration_tests.rs +++ b/tests/src/coffee_integration_tests.rs @@ -33,7 +33,7 @@ pub async fn init_coffee_test_cmd() -> anyhow::Result<()> { let dir = Arc::new(tempfile::tempdir()?); let args = CoffeeTestingArgs { conf: None, - data_dir: dir.path().clone().to_str().unwrap().to_owned(), + data_dir: dir.path().to_str().unwrap().to_owned(), network: "bitcoin".to_string(), }; let mut manager = CoffeeTesting::tmp_with_args(&args, dir.clone()).await?; @@ -48,7 +48,7 @@ pub async fn init_coffee_test_cmd() -> anyhow::Result<()> { drop(manager); let new_args = CoffeeTestingArgs { conf: None, - data_dir: dir.path().clone().to_string_lossy().to_string(), + data_dir: dir.path().to_string_lossy().to_string(), network: "testnet".to_string(), }; let mut manager = CoffeeTesting::tmp_with_args(&new_args, dir.clone()).await?; @@ -145,6 +145,27 @@ pub async fn test_add_remove_plugins() { .await .unwrap(); + // Get the list of plugins available in the remote repository + let result = manager.coffee().get_plugins_in_remote(repo_name).await; + assert!(result.is_ok(), "{:?}", result); + let result = result.unwrap(); + let plugins = result.plugins; + // Assert the length of the list of plugins is greater than 0 + assert!( + plugins.len() > 0, + "The list of plugins is empty: {:?}", + plugins + ); + // Assert that the list of plugins contains the summary and helpme plugin + assert!( + plugins.iter().any(|plugin| plugin.name() == "summary"), + "Plugin 'summary' not found" + ); + assert!( + plugins.iter().any(|plugin| plugin.name() == "helpme"), + "Plugin 'helpme' not found" + ); + // Install summary plugin let result = manager.coffee().install("summary", true, false).await; assert!(result.is_ok(), "{:?}", result); @@ -316,7 +337,7 @@ pub async fn install_plugin_in_two_networks() -> anyhow::Result<()> { let dir = Arc::new(tempfile::tempdir()?); let args = CoffeeTestingArgs { conf: None, - data_dir: dir.path().clone().to_str().unwrap().to_owned(), + data_dir: dir.path().to_str().unwrap().to_owned(), network: "regtest".to_string(), }; let mut manager = CoffeeTesting::tmp_with_args(&args, dir.clone()).await?; @@ -351,7 +372,7 @@ pub async fn install_plugin_in_two_networks() -> anyhow::Result<()> { log::info!("lightning path for testnet network: {lightning_dir}"); let new_args = CoffeeTestingArgs { conf: None, - data_dir: dir.path().clone().to_string_lossy().to_string(), + data_dir: dir.path().to_string_lossy().to_string(), network: "testnet".to_string(), }; let mut manager = CoffeeTesting::tmp_with_args(&new_args, dir.clone()).await?; @@ -389,7 +410,7 @@ pub async fn install_plugin_in_two_networks() -> anyhow::Result<()> { pub async fn test_double_slash() { init(); - let mut cln = Node::tmp("regtest").await.unwrap(); + let cln = Node::tmp("regtest").await.unwrap(); let mut manager = CoffeeTesting::tmp().await.unwrap(); let lightning_dir = cln.rpc().getinfo().unwrap().ligthning_dir; From 7e788c3617387c73f2ed64f90d0806d7f1a55a51 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 2 Oct 2023 21:35:43 +0300 Subject: [PATCH 2/3] feat(httpd): add an endpoint for coffee_list_plugins_remote Signed-off-by: Tarek --- coffee_httpd/src/httpd/server.rs | 15 ++++++++++ tests/src/coffee_httpd_integration_tests.rs | 31 +++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/coffee_httpd/src/httpd/server.rs b/coffee_httpd/src/httpd/server.rs index 743f89eb..32a6fe33 100644 --- a/coffee_httpd/src/httpd/server.rs +++ b/coffee_httpd/src/httpd/server.rs @@ -61,6 +61,7 @@ pub async fn run_httpd( .service(coffee_remote_list) .service(coffee_show) .service(coffee_search) + .service(coffee_list_plugins_in_remote) .with_json_spec_at("/api/v1") .build() }) @@ -158,6 +159,20 @@ async fn coffee_remote_list(data: web::Data) -> Result, Er handle_httpd_response!(result) } +#[api_v2_operation] +#[get("/remote/list_plugins")] +async fn coffee_list_plugins_in_remote( + data: web::Data, + body: Json, +) -> Result, Error> { + let repository_name = &body.repository_name; + + let coffee = data.coffee.lock().await; + let result = coffee.get_plugins_in_remote(repository_name).await; + + handle_httpd_response!(result) +} + #[api_v2_operation] #[get("/show")] async fn coffee_show(data: web::Data, body: Json) -> Result, Error> { diff --git a/tests/src/coffee_httpd_integration_tests.rs b/tests/src/coffee_httpd_integration_tests.rs index 8a4ecc4b..8708df78 100644 --- a/tests/src/coffee_httpd_integration_tests.rs +++ b/tests/src/coffee_httpd_integration_tests.rs @@ -103,6 +103,37 @@ pub async fn httpd_add_remove_plugins() { let body = response.text().await.unwrap(); log::info!("/remote/add response: {}", body); + // Define the request body to be sent to the /remote/list_plugins endpoint + let remote_plugins_list_request = RemotePluginsList { + repository_name: "lightningd".to_string(), + }; + + let response = client + .get(format!("{}/remote/list_plugins", url)) + .json(&remote_plugins_list_request) + .send() + .await; + assert!(response.is_ok(), "{:?}", response); + let response = response.unwrap(); + + let body = response.json::().await; + assert!(body.is_ok(), "{:?}", body); + let body = body.unwrap(); + + // Log the response body + log::info!("/remote/list_plugins response: {}", body); + + // Assert the response plugin list is not empty + let plugins = body["plugins"].as_array(); + assert!(plugins.is_some(), "{:?}", plugins); + let plugins = plugins.unwrap(); + + // Assert that the "helpme" plugin exists in the response + assert!( + plugins.iter().any(|plugin| plugin["name"] == "helpme"), + "helpme plugin not found in the response" + ); + // Define the request body to be sent to the /show endpoint let show_request = Show { plugin: "helpme".to_string(), From d39fa6f85af8434104158df1e083069c40541dc0 Mon Sep 17 00:00:00 2001 From: Tarek Date: Mon, 2 Oct 2023 21:35:57 +0300 Subject: [PATCH 3/3] =?UTF-8?q?=C2=A0feat(docs):=20add=20docs=20for=20coff?= =?UTF-8?q?ee=20remote=20=20--plugins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tarek --- docs/docs-book/src/using-coffee.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/docs-book/src/using-coffee.md b/docs/docs-book/src/using-coffee.md index 0a89857a..abada8fc 100644 --- a/docs/docs-book/src/using-coffee.md +++ b/docs/docs-book/src/using-coffee.md @@ -68,6 +68,14 @@ To list plugin repositories, simply run the following command. coffee remote list ``` +To list available plugins in a specific remote repository + +> ✅ Implemented + +```bash +coffee remote --plugins +``` + ### Install a Plugin > ✅ Implemented