Skip to content

Commit

Permalink
Added cli context funcionalities
Browse files Browse the repository at this point in the history
  • Loading branch information
petarjuki7 committed Jun 25, 2024
1 parent 2a76073 commit c26f05b
Show file tree
Hide file tree
Showing 13 changed files with 428 additions and 3 deletions.
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions crates/application/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
use std::fs::{self, File};
use std::io::Write;
#[cfg(unix)]
use std::os::unix::fs::symlink;
#[cfg(windows)]
use std::os::windows::fs::symlink_file as symlink;

use calimero_network::client::NetworkClient;
use camino::Utf8PathBuf;
Expand Down Expand Up @@ -55,6 +59,28 @@ impl ApplicationManager {
return Ok(());
}

pub async fn install_dev_application(
&self,
application_id: calimero_primitives::application::ApplicationId,
version: &semver::Version,
path: Option<Utf8PathBuf>,
) -> eyre::Result<()> {
self.link_release(
&application_id,
&self.application_dir,
version,
&path.unwrap(),
)?;

let topic_hash = self
.network_client
.subscribe(calimero_network::types::IdentTopic::new(application_id))
.await?;

info!(%topic_hash, "Subscribed to network topic");
return Ok(());
}

pub async fn list_installed_applications(
&self,
) -> eyre::Result<Vec<calimero_primitives::application::Application>> {
Expand Down Expand Up @@ -187,6 +213,31 @@ impl ApplicationManager {
Ok(())
}

fn link_release(
&self,
application_id: &calimero_primitives::application::ApplicationId,
dir: &camino::Utf8Path,
version: &semver::Version,
link_path: &camino::Utf8Path,
) -> eyre::Result<()> {
let base_path = format!("{}/{}/{}", dir, application_id, version);
info! {"{}", base_path};
fs::create_dir_all(&base_path)?;

let file_path = format!("{}/binary.wasm", base_path);
info!("{}", file_path);
match symlink(link_path, &file_path) {
Ok(_) => {}
Err(err) => eyre::bail!("Symlinking failed: {}", err),
}
info!(
"Application {} linked to node\nPath to linked file at {}",
application_id, file_path
);

Ok(())
}

async fn verify_release(&self, hash: &str, release_hash: &str) -> eyre::Result<()> {
if hash != release_hash {
return Err(eyre::eyre!(
Expand Down
4 changes: 4 additions & 0 deletions crates/meroctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ eyre.workspace = true
hex.workspace = true
libp2p.workspace = true
multiaddr.workspace = true
reqwest = { workspace = true, features = ["json"] }
semver.workspace = true
serde = { workspace = true, features = ["derive"] }
serde_json.workspace = true
sha2.workspace = true
tokio = { workspace = true, features = ["io-std", "macros"] }
toml.workspace = true
Expand All @@ -30,4 +33,5 @@ calimero-application = { path = "../application" }
calimero-node-primitives = { path = "../node-primitives" }
calimero-primitives = { path = "../primitives" }
calimero-server = { path = "../server", features = ["jsonrpc", "websocket", "admin"] }
calimero-server-primitives = { path = "../server-primitives" }
calimero-store = { path = "../store" }
9 changes: 6 additions & 3 deletions crates/meroctl/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use clap::{Parser, Subcommand};
use crate::config as struct_config;

mod config;
mod context;
mod init;
mod run;
#[derive(Debug, Parser)]
Expand All @@ -20,6 +21,7 @@ pub enum SubCommands {
Init(init::InitCommand),
Config(config::ConfigCommand),
Run(run::RunCommand),
Context(context::ContextCommand),
}

#[derive(Debug, Parser)]
Expand All @@ -37,9 +39,10 @@ pub struct RootArgs {
impl RootCommand {
pub async fn run(self) -> eyre::Result<()> {
match self.action {
SubCommands::Init(init) => return init.run(self.args),
SubCommands::Config(config) => return config.run(self.args),
SubCommands::Run(run) => return run.run(self.args).await,
SubCommands::Init(init) => init.run(self.args),
SubCommands::Config(config) => config.run(self.args),
SubCommands::Run(run) => run.run(self.args).await,
SubCommands::Context(context) => context.run(self.args).await,
}
}
}
179 changes: 179 additions & 0 deletions crates/meroctl/src/cli/context/create.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use calimero_primitives::application::ApplicationId;
use calimero_primitives::identity::Context;
use calimero_server_primitives::admin::ApplicationListResult;
use camino::Utf8PathBuf;
use clap::Parser;
use eyre::eyre;
use reqwest::{Client, Url};
use semver::Version;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use tracing::info;

use super::get_ip;
use crate::cli::RootArgs;
use crate::config::ConfigFile;

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateContextRequest {
application_id: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct CreateContextResponse {
data: Context,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct InstallApplicationRequest {
pub application: ApplicationId,
pub version: Version,
pub path: Option<Utf8PathBuf>,
}

#[derive(Debug, Serialize, Deserialize)]
struct ListApplicationsResponse {
data: ApplicationListResult,
}

#[derive(Debug, Parser)]
pub struct CreateCommand {
/// The application ID to attach to the context
#[clap(long, short = 'a')]
application_id: Option<String>,

/// Enable dev mode
#[clap(long)]
dev: bool,

/// Path to use in dev mode (required in dev mode)
#[clap(short, long, requires = "dev")]
path: Option<Utf8PathBuf>,

/// Version of the application (required in dev mode)
#[clap(short, long, requires = "dev")]
version: Option<Version>,
}
impl CreateCommand {
pub async fn run(self, root_args: RootArgs) -> eyre::Result<()> {
let path = root_args.home.join(&root_args.node_name);
if ConfigFile::exists(&path) {
if let Ok(config) = ConfigFile::load(&path) {
let multiaddr = config
.network
.server
.listen
.first()
.ok_or_else(|| eyre!("No address."))?;
let base_url = get_ip(multiaddr)?;

match self.dev {
true => Ok(link_local_app(base_url, self.path, self.version).await?),
false => Ok(create_context(base_url, self.application_id).await?),
}
} else {
Err(eyre!("Failed to load config file"))
}
} else {
Err(eyre!("Config file does not exist"))
}
}
}

async fn create_context(base_url: Url, application_id: Option<String>) -> eyre::Result<()> {
let application_id =
application_id.ok_or_else(|| eyre!("Application ID is required for starting context"))?;

app_installed(&base_url, &application_id).await?;

let url = format!("{}admin-api/contexts-dev", base_url);
let client = Client::new();
let request = CreateContextRequest { application_id };

let response = client.post(&url).json(&request).send().await?;

if response.status().is_success() {
let context_response: CreateContextResponse = response.json().await?;
let context = context_response.data;

println!("Context created successfully:");
println!("ID: {}", context.id);
println!("Application ID: {}", context.application_id);
} else {
let status = response.status();
let error_text = response.text().await?;
return Err(eyre!(
"Request failed with status: {}. Error: {}",
status,
error_text
));
}
Ok(())
}

async fn app_installed(base_url: &Url, application_id: &String) -> eyre::Result<()> {
let url = format!("{}admin-api/applications-dev", base_url);
let client = Client::new();
let response = client.get(&url).send().await?;
if response.status().is_success() {
let api_response: ListApplicationsResponse = response.json().await?;
let app_list = api_response.data.apps;
let x = app_list
.iter()
.map(|app| app.id.0.clone())
.any(|app| app == *application_id);
if x {
return Ok(());
} else {
return Err(eyre!("The app with the id {} is not installed\nTo create a context, install an app first", application_id));
}
} else {
return Err(eyre!("Request failed with status: {}", response.status()));
}
}

async fn link_local_app(
base_url: Url,
path: Option<Utf8PathBuf>,
version: Option<Version>,
) -> eyre::Result<()> {
let path = path.ok_or_else(|| eyre!("Path is required in dev mode"))?;
let version = version.ok_or_else(|| eyre!("Version is required in dev mode"))?;

let install_url = format!("{}admin-api/install-dev-application", base_url);

let id = format!("{}:{}", version, path);
let mut hasher = Sha256::new();
hasher.update(id.as_bytes());
let application_id = hex::encode(hasher.finalize());

let client = Client::new();
let install_request = InstallApplicationRequest {
application: ApplicationId(application_id.clone()),
version: version,
path: Some(path),
};

let install_response = client
.post(&install_url)
.json(&install_request)
.send()
.await?;

if !install_response.status().is_success() {
let status = install_response.status();
let error_text = install_response.text().await?;
return Err(eyre!(
"Application installation failed with status: {}. Error: {}",
status,
error_text
));
}

info!("Application installed successfully.");

create_context(base_url, Some(application_id)).await?;

Ok(())
}
57 changes: 57 additions & 0 deletions crates/meroctl/src/cli/context/ls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use calimero_primitives::identity::Context;
use clap::Parser;
use eyre::eyre;
use reqwest::Client;
use serde::{Deserialize, Serialize};

use super::get_ip;
use crate::cli::RootArgs;
use crate::config::ConfigFile;
#[derive(Debug, Serialize, Deserialize)]
pub struct GetContextsResponse {
data: Vec<Context>,
}

#[derive(Debug, Parser)]
pub struct LsCommand {}

impl LsCommand {
pub async fn run(self, root_args: RootArgs) -> eyre::Result<()> {
let path = root_args.home.join(&root_args.node_name);
if ConfigFile::exists(&path) {
if let Ok(config) = ConfigFile::load(&path) {
let multiaddr = config
.network
.server
.listen
.first()
.ok_or_else(|| eyre!("No address."))?;
let base_url = get_ip(multiaddr)?;
let url = format!("{}admin-api/contexts-dev", base_url);

let client = Client::new();
let response = client.get(&url).send().await?;

if response.status().is_success() {
let api_response: GetContextsResponse = response.json().await?;
let contexts = api_response.data;

println!("Contexts:");
for context in contexts {
println!("App ID: {}", context.application_id);
println!("Context ID: {}", context.id);
println!();
}
} else {
return Err(eyre!("Request failed with status: {}", response.status()));
}
} else {
return Err(eyre!("Failed to load config file"));
}
} else {
return Err(eyre!("Config file does not exist"));
}

Ok(())
}
}
13 changes: 13 additions & 0 deletions crates/meroctl/src/cli/context/members.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use clap::Parser;

use crate::cli::RootArgs;

#[derive(Debug, Parser)]
pub struct MembersCommand {}

impl MembersCommand {
pub async fn run(self, _root_args: RootArgs) -> eyre::Result<()> {
println!("Running members command");
Ok(())
}
}
Loading

0 comments on commit c26f05b

Please sign in to comment.