Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Command to List Plugins in a Remote Repository #200

Merged
merged 3 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions coffee_cmd/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ pub enum CoffeeCommand {
#[clap(arg_required_else_help = true)]
Remote {
#[clap(subcommand)]
action: RemoteAction,
action: Option<RemoteAction>,
#[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<String>,
},
/// Configure coffee with the core lightning
/// configuration
Expand All @@ -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 {},
}

Expand All @@ -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()),
Expand Down
91 changes: 64 additions & 27 deletions coffee_cmd/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions coffee_core/src/coffee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,16 @@ impl PluginManager for CoffeeManager {
})
}

async fn get_plugins_in_remote(&self, name: &str) -> Result<CoffeeList, CoffeeError> {
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<CoffeeShow, CoffeeError> {
for repo in self.repos.values() {
if let Some(plugin) = repo.get_plugin_by_name(plugin) {
Expand Down
2 changes: 1 addition & 1 deletion coffee_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub enum CoffeeOperation {
Upgrade(String),
Remove(String),
/// Remote(name repository, url of the repository)
Remote(RemoteAction),
Remote(Option<RemoteAction>, bool, Option<String>),
/// Setup(core lightning root path)
Setup(String),
Show(String),
Expand Down
15 changes: 15 additions & 0 deletions coffee_httpd/src/httpd/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ pub async fn run_httpd<T: ToSocketAddrs>(
.service(coffee_remote_list)
.service(coffee_show)
.service(coffee_search)
.service(coffee_list_plugins_in_remote)
.with_json_spec_at("/api/v1")
.build()
})
Expand Down Expand Up @@ -158,6 +159,20 @@ async fn coffee_remote_list(data: web::Data<AppState>) -> Result<Json<Value>, Er
handle_httpd_response!(result)
}

#[api_v2_operation]
#[get("/remote/list_plugins")]
async fn coffee_list_plugins_in_remote(
data: web::Data<AppState>,
body: Json<RemotePluginsList>,
) -> Result<Json<Value>, 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<AppState>, body: Json<Show>) -> Result<Json<Value>, Error> {
Expand Down
3 changes: 3 additions & 0 deletions coffee_lib/src/plugin_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ pub trait PluginManager {
/// list the remote repositories for the plugin manager.
async fn list_remotes(&mut self) -> Result<CoffeeRemote, CoffeeError>;

/// List the plugins available in a remote repository.
async fn get_plugins_in_remote(&self, name: &str) -> Result<CoffeeList, CoffeeError>;

/// set up the core lightning configuration target for the
/// plugin manager.
async fn setup(&mut self, cln_conf_path: &str) -> Result<(), CoffeeError>;
Expand Down
10 changes: 10 additions & 0 deletions coffee_lib/src/types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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<Plugin>,
Expand Down
8 changes: 8 additions & 0 deletions docs/docs-book/src/using-coffee.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <repository_name> --plugins
```

### Install a Plugin

> ✅ Implemented
Expand Down
31 changes: 31 additions & 0 deletions tests/src/coffee_httpd_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<serde_json::Value>().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(),
Expand Down
31 changes: 26 additions & 5 deletions tests/src/coffee_integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
Expand All @@ -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?;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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?;
Expand Down Expand Up @@ -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;
Expand Down
Loading