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

feat: add Extensions Manager #68

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
31cfbf5
add confido extension
pavelnikonorov Nov 7, 2024
4f0d08d
update version
pavelnikonorov Nov 25, 2024
2973865
code cleanup
pavelnikonorov Nov 7, 2024
326f7fb
add extension checksum verification
pavelnikonorov Nov 25, 2024
b43f83a
update user config directory path across all usage places
pavelnikonorov Nov 13, 2024
827745e
add extensions manager
pavelnikonorov Nov 7, 2024
5a3e2d8
add handling of user config dir absence
pavelnikonorov Nov 13, 2024
68f0c76
update configuration handling
pavelnikonorov Nov 7, 2024
883ce15
fix: tes create url - rebase to base_path
pavelnikonorov Nov 7, 2024
6e98029
refactor: enable/disable extension
pavelnikonorov Nov 7, 2024
c9b661b
update confido extension definition file, new version
pavelnikonorov Nov 14, 2024
04c3c71
code cleanup
pavelnikonorov Nov 22, 2024
defe5ed
add extension enable/disable methods
pavelnikonorov Nov 7, 2024
58913de
add cli --verbose flag
pavelnikonorov Nov 7, 2024
ce255f5
update dependecies
pavelnikonorov Nov 7, 2024
231e53f
update/fix clients
pavelnikonorov Nov 7, 2024
9fbbc10
fix: unexpected extension removal
pavelnikonorov Nov 7, 2024
2c041de
fix: extensions config pointer isshue
pavelnikonorov Nov 7, 2024
89d2a2a
cli task create output cleanup
pavelnikonorov Nov 7, 2024
43f6b67
update expected user config folder path
pavelnikonorov Nov 13, 2024
eae23f2
add transport support for extensible TLS verifiers
pavelnikonorov Nov 7, 2024
f8e3b90
update CLI
pavelnikonorov Nov 7, 2024
4f4ad0e
update documentation
pavelnikonorov Nov 25, 2024
ebcab23
update CLI
pavelnikonorov Nov 25, 2024
08c4265
update Configuration::default()
pavelnikonorov Nov 7, 2024
2d576d5
CLI/SDK: added extension add/remove methods
pavelnikonorov Nov 7, 2024
7b277d2
code clean-up
pavelnikonorov Nov 25, 2024
df11c29
update README.md
pavelnikonorov Nov 25, 2024
87fed02
update confido extension file; add README
pavelnikonorov Nov 27, 2024
d068f2d
version fix
pavelnikonorov Nov 27, 2024
8898e70
version fix
pavelnikonorov Nov 27, 2024
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
10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ members = [
"cli",
"python"
]
resolver = "2" # Use the edition 2021 resolver

[workspace.package]
version = "0.1.0"
authors = ["Aarav Mehta <[email protected]>", "Pavel Nikonorov <[email protected]>", "ELIXIR Cloud & AAI <[email protected]> (ELIXIR Europe)"]
version = "0.1.6"

authors = [
"Aarav Mehta <[email protected]> (Google Summer of Code Contributor)",
"Pavel Nikonorov <[email protected]> (Google Summer of Code Mentor, GENXT)",
"ELIXIR Cloud & AAI <[email protected]> (ELIXIR Europe)"
]
edition = "2021"
readme = "./README.md"
license-file = "LICENSE"
Expand Down
1 change: 1 addition & 0 deletions VERSION
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0.1.6
14 changes: 8 additions & 6 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,28 +1,30 @@
[package]
name = "ga4gh-cli"
description = """
A cross-platform command-line tool leveraging the GA4GH SDK
"""
description = "A versatile, cross-platform command-line tool built around the GA4GH-SDK for working with GA4GH federated cloud environments."
repository = "https://github.com/elixir-cloud-aai/ga4gh-sdk/tree/main/cli"
version.workspace = true
authors.workspace = true
edition.workspace = true
readme.workspace = true
license-file.workspace = true
readme = "README.md"
build = "build.rs"

[dependencies]
ga4gh-lib = { path = "../lib" }
clap = "3.0"
clap_complete = "3.0"
tokio = { version = "1", features = ["full"] }
serde_json = "^1.0"
serde_json = "1.0.0"
tempfile = "3.2"
dirs = "5.0.1"
anyhow = "1.0.86"
url = "2.5.2"
log = "0.4.22"
env_logger = "0.11.5"
rusoto_core = "0.48.0"
rusoto_s3 = "0.48.0"
reqwest = { version = "0.11", features = ["json"] }

[[bin]]
name = "cli"
name = "ga4gh-cli"
path = "src/main.rs"
43 changes: 42 additions & 1 deletion cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ The `ga4gh-cli` is a command line tool that leverages `ga4gh-sdk` library to pro

## Configuration

The `ga4gh-cli` expects configuration in `~/.ga4gh-cli/config.json` file.
The `ga4gh-cli` expects configuration in `~/.ga4gh/config.json` file.

### Configuration Example

Expand Down Expand Up @@ -39,6 +39,8 @@ funnel server start --Server.HTTPPort=[available-port]

### CLI Examples

#### Working with GA4GH Task Execution Service instances

1. To create a new task run the `tes create` command. You can provide task definition json data as text:

```sh
Expand Down Expand Up @@ -103,3 +105,42 @@ ga4gh-cli tes status [TASK-ID]
```sh
ga4gh-cli tes cancel [TASK-ID]
```

#### Adding an Extension

1. To add an extension, use the following command:

```sh
ga4gh-cli extension add path/to/extension.json
```

2. After adding the extension, enable it with this command:

```sh
ga4gh-cli extension enable [extension-name]
```

3. List installed extensions:

```sh
ga4gh-cli extension list
```

Example output:
```
Extension Name Enabled
confido true
```


4. Disable the extension:

```sh
ga4gh-cli extension disable [extension-name]
```

5. Remove the extension:

```sh
ga4gh-cli extension remove [extension-name]
```
161 changes: 131 additions & 30 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,41 @@
use ga4gh_sdk::clients::tes::{Task, TES};
use ga4gh_sdk::utils::configuration::Configuration;
use ga4gh_sdk::utils::transport::Transport;
use ga4gh_sdk::utils::extension::InstalledExtension;
use ga4gh_sdk::clients::ServiceType;
use ga4gh_sdk::clients::tes::models::ListTasksParams;
use ga4gh_sdk::clients::tes::models::TesListTasksResponse;
use ga4gh_sdk::clients::tes::models::TesState;
use ga4gh_sdk::clients::tes::models::TesTask;
use clap::{arg, Command};
use clap::{arg, Arg, Command};
use std::path::Path;
use std::error::Error;
use log::{debug, error};
use ga4gh_sdk::utils::expand_path_with_home_dir;
use std::env;
use std::fs;

const VERSION: &str = env!("PACKAGE_VERSION");

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
env_logger::init();

let cmd = Command::new("ga4gh-cli")
.bin_name("cli")
.version("0.1.0")
.about("CLI to manage tasks")
let cmd = Command::new("GA4GH-CLI")
.bin_name("ga4gh-cli")
.version(VERSION)
.about("GA4GH-CLI is a versatile command-line interface for GA4GH federated cloud environments, \
built on the GA4GH-SDK Rust library. Designed to simplify interactions with core API services, \
it currently supports TES, with plans to expand to WES, DRS, TRS, and AAI.\n\
Contributors are welcome: https://github.com/elixir-cloud-aai/ga4gh-sdk")
.subcommand_required(true)
.arg_required_else_help(true)
.arg(
Arg::new("verbose")
.long("verbose")
.takes_value(true)
.possible_values(&["trace", "debug", "info", "warn", "error", "off"])
.default_value("info")
.help("Sets the level of verbosity"),
)
.subcommand(
Command::new("tes")
.about("TES subcommands")
Expand Down Expand Up @@ -62,15 +77,65 @@ async fn main() -> Result<(), Box<dyn Error>> {
.about("cancel the task")
.arg(arg!(<id> "The id of the task which should be cancel"))
.arg_required_else_help(true),
),
)
)
.subcommand(
Command::new("extension")
.about("Extension subcommands")
.subcommand_required(true)
.arg_required_else_help(true)
.subcommand(
Command::new("list")
.about("List all extensions"),
)
.subcommand(
Command::new("add")
.about("Load an extension")
.arg(arg!(<file> "The extension file to load"))
.arg_required_else_help(true),
)
.subcommand(
Command::new("remove")
.about("Unload an extension")
.arg(arg!(<name> "The name of the extension to unload"))
.arg_required_else_help(true),
)
.subcommand(
Command::new("enable")
.about("Enable an extension")
.arg(arg!(<name> "The name of the extension to unload"))
.arg_required_else_help(true),
)
.subcommand(
Command::new("disable")
.about("Enable an extension")
.arg(arg!(<name> "The name of the extension to unload"))
.arg_required_else_help(true),
)
);

let matches = cmd.clone().get_matches();

match matches.subcommand() {
Some(("tes", sub)) => {
let config = Configuration::from_file(ServiceType::TES)?;
let log_level = matches.value_of("verbose").unwrap_or("info");
env::set_var("RUST_LOG", log_level);
env_logger::init();

let config_dir_path = expand_path_with_home_dir(".ga4gh");
if !Path::new(config_dir_path.as_str()).exists() {
debug!("Creating directory: {}", config_dir_path);
if let Err(e) = fs::create_dir_all(&config_dir_path) {
error!("Failed to create directory: {}", e);
}
}

let service_config_path = expand_path_with_home_dir(".ga4gh/config.json");
let extensions_config_path = expand_path_with_home_dir(".ga4gh/extensions.json");

match matches.subcommand() {
Some(("tes", sub)) => {
let config = Configuration::from_file(Some(ServiceType::TES), &service_config_path, &extensions_config_path)?;
let transport = Transport::new(&config)?;

if let Some(("create", sub)) = sub.subcommand() {
let task_file = sub.value_of("TASK_FILE")
.ok_or_else(|| anyhow::anyhow!("TASK_FILE argument is required"))?;
Expand All @@ -86,13 +151,14 @@ async fn main() -> Result<(), Box<dyn Error>> {
task_file.to_string()
},
};

let testask: TesTask = serde_json::from_str(&task_json)
.map_err(|e| format!("Failed to parse JSON: {}", e))?;
.map_err(|e| format!("Failed to parse GA4GH TES Task JSON: {}", e))?;

match TES::new(&config).await {
Ok(tes) => {
let task = tes.create(testask).await;
println!("{:?}", task);
let task = tes.create(testask).await?;
println!("TASK ID: {}", task.id);
},
Err(e) => {
error!("Error creating TES instance: {:?}", e);
Expand Down Expand Up @@ -142,23 +208,15 @@ async fn main() -> Result<(), Box<dyn Error>> {
if let Some(("get", sub)) = sub.subcommand() {
let id = sub.value_of("id").unwrap();
let view = sub.value_of("view").unwrap();

match TES::new(&config).await {
Ok(tes) => {
let task = tes.get(view, id).await;
println!("{:?}", task);
},
Err(e) => {
error!("Error creating TES instance: {:?}", e);
return Err(e);
}
};
let tes = TES::new(&config).await?;
let task = tes.get(view, id).await?;
let task_pretty_json = serde_json::to_string_pretty(&task).unwrap();
println!("{}", task_pretty_json);
}

if let Some(("status", sub)) = sub.subcommand() {
let id = sub.value_of("id").unwrap().to_string();
let transport = Transport::new(&config);
let task = Task::new(id.clone(), transport);
let task = Task::new(id.clone(), transport.clone());
match task.status().await {
Ok(status) => {
println!("TASKID: {}", id.clone());
Expand All @@ -173,8 +231,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

if let Some(("cancel", sub)) = sub.subcommand() {
let id = sub.value_of("id").unwrap().to_string();
let transport = Transport::new(&config);
let task = Task::new(id, transport);
let task = Task::new(id, transport.clone());
match task.cancel().await {
Ok(output) => {
println!("STATUS: {:?}", output);
Expand All @@ -186,6 +243,35 @@ async fn main() -> Result<(), Box<dyn Error>> {
};
}
}

Some(("extension", sub)) => {
let config = Configuration::from_file(None, &service_config_path, &extensions_config_path)?;

if let Some(("list", _)) = sub.subcommand() {
let extensions = config.extensions_manager.get_extensions();
println!("{}", format_extensions(extensions));
}

if let Some(("add", sub)) = sub.subcommand() {
let file = sub.value_of("file").unwrap();
config.extensions_manager.add_extension(file).await?;
}

if let Some(("remove", sub)) = sub.subcommand() {
let name = sub.value_of("name").unwrap();
config.extensions_manager.remove_extension(name)?;
}

if let Some(("enable", sub)) = sub.subcommand() {
let file = sub.value_of("name").unwrap();
config.extensions_manager.enable_extension(file)?;
}

if let Some(("disable", sub)) = sub.subcommand() {
let file = sub.value_of("name").unwrap();
config.extensions_manager.disable_extension(file)?;
}
}

_ => {
error!("Error: Unrecognized command or option");
Expand Down Expand Up @@ -230,4 +316,19 @@ fn format_tasks_response(response: &TesListTasksResponse) -> String {
table.push_str(&format_task(task));
}
table
}

fn format_extensions(extensions: &Vec<InstalledExtension>) -> String {
let mut table = String::new();
let headers = format!("{:<25} {:<15}\n", "Extension Name", "Enabled");
table.push_str(&headers);
for extension in extensions {
let row = format!(
"{:<25} {:<15}\n",
extension.name.as_str(),
extension.enabled
);
table.push_str(&row);
}
table
}
3 changes: 3 additions & 0 deletions extensions/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Temporary Extensions Folder

This is a temporary repository for extensions. An external repository will be developed in the future.
Loading