From 9faad4ef2e5650d7856a2c21daac3bd52a14a18f Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Wed, 7 Jan 2026 14:45:39 +0100 Subject: [PATCH 1/4] [#167]: Fixed api/main.rs info! typos --- core/api/src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/api/src/main.rs b/core/api/src/main.rs index 0843684..30fe550 100644 --- a/core/api/src/main.rs +++ b/core/api/src/main.rs @@ -1,12 +1,12 @@ // module imports -use tonic::transport::{Error, Server}; use cortexbrain_common::logger; +use tonic::transport::{Error, Server}; mod agent; mod api; -mod structs; mod constants; mod helpers; +mod structs; mod agent_proto { use tonic::include_file_descriptor_set; @@ -26,7 +26,7 @@ async fn main() -> Result<(), Error> { logger::init_default_logger(); info!("Starting agent server..."); - info!("fetching data"); + info!("Fetching data"); //FIXME: binding on 0.0.0.0 address is not ideal for a production environment. This will need future fixes let address = "0.0.0.0:9090".parse().unwrap(); @@ -37,7 +37,7 @@ async fn main() -> Result<(), Error> { .build_v1() { Ok(reflection_server) => { - info!("reflection server started correctly"); + info!("Reflection server started correctly"); match Server::builder() .add_service(AgentServer::new(api)) .add_service(reflection_server) @@ -46,7 +46,7 @@ async fn main() -> Result<(), Error> { { Ok(_) => info!("Server started with no errors"), Err(e) => error!( - "An error occured during the Server::builder processe. Error {}", + "An error occured during the Server::builder process. Error {}", e ), } From 745f360b67865a28be7c6105d60872a6fec4fc30 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Wed, 7 Jan 2026 15:11:27 +0100 Subject: [PATCH 2/4] [#167]: fixed installation bug. Added metrics.yaml in the installation files --- cli/src/install.rs | 183 ++++++++++++++++++++++++++----------------- cli/src/uninstall.rs | 88 ++++++++++----------- 2 files changed, 152 insertions(+), 119 deletions(-) diff --git a/cli/src/install.rs b/cli/src/install.rs index 644212d..a24fc22 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -1,11 +1,11 @@ -use colored::Colorize; -use kube::{ core::ErrorResponse }; -use clap::{ Args, Subcommand, command }; -use std::{ process::{ Command }, thread, time::Duration }; -use crate::{ - essential::{ connect_to_client, create_config_file, create_configs, BASE_COMMAND, CliError }, +use crate::essential::{ + BASE_COMMAND, CliError, connect_to_client, create_config_file, create_configs, }; +use clap::{Args, Subcommand, command}; +use colored::Colorize; use kube::Error; +use kube::core::ErrorResponse; +use std::{process::Command, thread, time::Duration}; // docs: // @@ -29,7 +29,10 @@ enum InstallationType { #[derive(Subcommand, Debug, Clone)] pub enum InstallCommands { - #[command(name = "cortexflow", about = "Install all the CortexBrain core components")] + #[command( + name = "cortexflow", + about = "Install all the CortexBrain core components" + )] All, #[command( name = "simple-example", @@ -51,9 +54,21 @@ pub struct InstallArgs { // This function creates the cortexflow namespace, manages the metadata file creation and removes the temporary installation files pub async fn install_cortexflow() -> Result<(), CliError> { - println!("{} {}", "=====>".blue().bold(), "Preparing cortexflow installation".white()); - println!("{} {}", "=====>".blue().bold(), "Creating the config files".white()); - println!("{} {}", "=====>".blue().bold(), "Creating cortexflow namespace".white()); + println!( + "{} {}", + "=====>".blue().bold(), + "Preparing cortexflow installation".white() + ); + println!( + "{} {}", + "=====>".blue().bold(), + "Creating the config files".white() + ); + println!( + "{} {}", + "=====>".blue().bold(), + "Creating cortexflow namespace".white() + ); Command::new("kubectl") .args(["create", "namespace", "cortexflow"]) .output() @@ -71,7 +86,11 @@ pub async fn install_cortexflow() -> Result<(), CliError> { // This function installs the demostration examples pub async fn install_simple_example() -> Result<(), CliError> { - println!("{} {}", "=====>".blue().bold(), "Installing simple example".white()); + println!( + "{} {}", + "=====>".blue().bold(), + "Installing simple example".white() + ); install_simple_example_component().await?; Ok(()) } @@ -90,47 +109,47 @@ pub async fn install_simple_example() -> Result<(), CliError> { async fn install_cluster_components() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { - println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); + println!( + "{} {}", + "=====>".blue().bold(), + "Copying installation files".white() + ); download_installation_files( InstallationType::Components( vec![ "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/configmap-role.yaml".to_string(), "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/rolebinding.yaml".to_string(), "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/cortexflow-rolebinding.yaml".to_string(), - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/feature/ebpf-core/core/src/testing/identity.yaml".to_string(), - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/feature/ebpf-core/core/src/testing/agent.yaml".to_string() + "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/identity.yaml".to_string(), + "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/agent.yaml".to_string(), + "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/metrics.yaml".to_string() ] ) )?; thread::sleep(Duration::from_secs(1)); install_components("cortexbrain")?; println!("\n"); - rm_installation_files( - InstallationType::Components( - vec![ - "configmap-role.yaml".to_string(), - "rolebinding.yaml".to_string(), - "cortexflow-rolebinding.yaml".to_string(), - "identity.yaml".to_string(), - "agent.yaml".to_string() - ] - ) - )?; - println!("{} {}", "=====>".blue().bold(), "installation completed".white()); + rm_installation_files(InstallationType::Components(vec![ + "configmap-role.yaml".to_string(), + "rolebinding.yaml".to_string(), + "cortexflow-rolebinding.yaml".to_string(), + "identity.yaml".to_string(), + "metrics.yaml".to_string(), + "agent.yaml".to_string(), + ]))?; + println!( + "{} {}", + "=====>".blue().bold(), + "installation completed".white() + ); Ok(()) } - Err(e) => { - Err( - CliError::ClientError( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(e) => Err(CliError::ClientError(Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } @@ -148,7 +167,11 @@ async fn install_cluster_components() -> Result<(), CliError> { async fn install_simple_example_component() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { - println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); + println!( + "{} {}", + "=====>".blue().bold(), + "Copying installation files".white() + ); download_installation_files( InstallationType::SimpleExample( "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/feature/ebpf-core/core/src/testing/deploy-test-pod.yaml".to_string() @@ -157,24 +180,22 @@ async fn install_simple_example_component() -> Result<(), CliError> { thread::sleep(Duration::from_secs(1)); install_components("simple-example")?; println!("\n"); - rm_installation_files( - InstallationType::SimpleExample("deploy-test-pod.yaml".to_string()) - )?; - println!("{} {}", "=====>".blue().bold(), "installation completed".white()); + rm_installation_files(InstallationType::SimpleExample( + "deploy-test-pod.yaml".to_string(), + ))?; + println!( + "{} {}", + "=====>".blue().bold(), + "installation completed".white() + ); Ok(()) } - Err(e) => { - Err( - CliError::ClientError( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(e) => Err(CliError::ClientError(Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } @@ -193,24 +214,29 @@ fn install_components(components_type: &str) -> Result<(), CliError> { "rolebinding.yaml", "cortexflow-rolebinding.yaml", "identity.yaml", - "agent.yaml" + "metrics.yaml", + "agent.yaml", ]; let tot_files = files_to_install.len(); - println!("{} {}", "=====>".blue().bold(), "Installing cortexflow components".white()); + println!( + "{} {}", + "=====>".blue().bold(), + "Installing cortexflow components".white() + ); let mut i = 1; for component in files_to_install { println!( - "{} {}{}{}{} {} {} {}", + "{} {}{}{}{}{} {} {}", "=====>".blue().bold(), "(", i, "/", tot_files, ")", - "Applying ", - component + "Applying", + component.to_string().green().bold() ); apply_component(component); i = i + 1; @@ -222,15 +248,15 @@ fn install_components(components_type: &str) -> Result<(), CliError> { for component in files_to_install { println!( - "{} {}{}{}{} {} {} {}", + "{} {}{}{}{}{} {} {}", "=====>".blue().bold(), "(", i, "/", tot_files, ")", - "Applying ", - component + "Applying", + component.to_string().green().bold() ); apply_component(component); i = i + 1; @@ -261,7 +287,11 @@ fn apply_component(file: &str) -> Result<(), CliError> { })?; if !output.status.success() { - eprintln!("Error installing file: {}:\n{}", file, String::from_utf8_lossy(&output.stderr)); + eprintln!( + "Error installing file: {}:\n{}", + file, + String::from_utf8_lossy(&output.stderr) + ); } else { println!("✅ Applied {}", file); } @@ -303,7 +333,11 @@ fn download_installation_files(installation_files: InstallationType) -> Result<( // Returns an CliError if something fails fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), CliError> { - println!("{} {}", "=====>".blue().bold(), "Removing temporary installation files".white()); + println!( + "{} {}", + "=====>".blue().bold(), + "Removing temporary installation files".white() + ); match file_to_remove { InstallationType::Components(files) => { for src in files.iter() { @@ -328,15 +362,20 @@ fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), CliErro // Returns a CliError if something fails fn download_file(src: &str) -> Result<(), CliError> { - let output = Command::new("wget") - .args([src]) - .output() - .map_err(|_| CliError::InstallerError { - reason: "An error occured: component download failed".to_string(), - })?; + let output = + Command::new("wget") + .args([src]) + .output() + .map_err(|_| CliError::InstallerError { + reason: "An error occured: component download failed".to_string(), + })?; if !output.status.success() { - eprintln!("Error copying file: {}.\n{}", src, String::from_utf8_lossy(&output.stderr)); + eprintln!( + "Error copying file: {}.\n{}", + src, + String::from_utf8_lossy(&output.stderr) + ); } else { println!("✅ Copied file from {} ", src); } diff --git a/cli/src/uninstall.rs b/cli/src/uninstall.rs index c28cff5..0d71cfa 100644 --- a/cli/src/uninstall.rs +++ b/cli/src/uninstall.rs @@ -1,8 +1,8 @@ use colored::Colorize; -use std::{ io::stdin, process::Command, time::Duration, thread }; +use std::{io::stdin, process::Command, thread, time::Duration}; -use crate::essential::{ BASE_COMMAND, CliError, connect_to_client }; -use kube::{ Error, core::ErrorResponse }; +use crate::essential::{BASE_COMMAND, CliError, connect_to_client}; +use kube::{Error, core::ErrorResponse}; //docs: // @@ -18,11 +18,17 @@ use kube::{ Error, core::ErrorResponse }; pub async fn uninstall() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { - println!("{} {}", "=====>".blue().bold(), "Uninstalling cortexflow..."); + println!( + "{} {}", + "=====>".blue().bold(), + "Uninstalling cortexflow..." + ); let mut userinput: String = String::new(); println!("{} {}", "=====>".blue().bold(), "Select one option:"); display_uninstall_options(); - stdin().read_line(&mut userinput).expect("Error reading user input"); + stdin() + .read_line(&mut userinput) + .expect("Error reading user input"); let trimmed_input = userinput.trim(); if trimmed_input == "1" { @@ -32,18 +38,12 @@ pub async fn uninstall() -> Result<(), CliError> { } Ok(()) } - Err(_) => { - Err( - CliError::ClientError( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(_) => Err(CliError::ClientError(Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } @@ -68,7 +68,11 @@ fn display_uninstall_options() { async fn uninstall_all() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { - println!("{} {}", "=====>".blue().bold(), "Deleting cortexflow components".red()); + println!( + "{} {}", + "=====>".blue().bold(), + "Deleting cortexflow components".red().bold() + ); let output = Command::new(BASE_COMMAND) .args(["delete", "namespace", "cortexflow"]) .output() @@ -87,18 +91,12 @@ async fn uninstall_all() -> Result<(), CliError> { }) } } - Err(_) => { - Err( - CliError::ClientError( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(_) => Err(CliError::ClientError(Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } @@ -111,13 +109,15 @@ async fn uninstall_all() -> Result<(), CliError> { // // Returns an InstallerError if something fails -async fn uninstall_component( - component_type: &str, - component: &str -) -> Result<(), CliError> { +async fn uninstall_component(component_type: &str, component: &str) -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { - println!("{} {} {}", "=====>".blue().bold(), "Deleting service", component); + println!( + "{} {} {}", + "=====>".blue().bold(), + "Deleting service", + component + ); let output = Command::new(BASE_COMMAND) .args(["delete", component_type, component, "-n", "cortexflow"]) @@ -137,18 +137,12 @@ async fn uninstall_component( }) } } - Err(_) => { - Err( - CliError::ClientError( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(_) => Err(CliError::ClientError(Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } From c9514c40500b683bc3be08054bfba8b99fd75c3f Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Wed, 7 Jan 2026 23:02:32 +0100 Subject: [PATCH 3/4] [#167] fixed cfcli update command. Added get_latest_cfcli_version to return the latest version from crates.io --- cli/src/essential.rs | 218 +++++++++++++++++++++++++++++-------------- 1 file changed, 150 insertions(+), 68 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 1a4d44f..3f4ae3e 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -1,12 +1,17 @@ -use std::{ collections::BTreeMap, fmt, process::Command, result::Result::Ok }; +use std::process::Output; +use std::thread; +use std::time::Duration; +use std::{collections::BTreeMap, fmt, process::Command, result::Result::Ok}; +use anyhow::Error; +use colored::Colorize; +use k8s_openapi::apimachinery::pkg::version; use kube::core::ErrorResponse; use serde::Serialize; -use colored::Colorize; use k8s_openapi::api::core::v1::ConfigMap; use k8s_openapi::serde_json::json; -use kube::api::{ Api, ObjectMeta, Patch, PatchParams, PostParams }; +use kube::api::{Api, ObjectMeta, Patch, PatchParams, PostParams}; use kube::client::Client; pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command @@ -41,17 +46,11 @@ pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command #[derive(Debug)] pub enum CliError { - InstallerError { - reason: String, - }, + InstallerError { reason: String }, ClientError(kube::Error), - UninstallError { - reason: String, - }, + UninstallError { reason: String }, AgentError(tonic_reflection::server::Error), - MonitoringError { - reason: String, - }, + MonitoringError { reason: String }, } // docs: // error type conversions @@ -63,12 +62,14 @@ impl From for CliError { } impl From for CliError { fn from(e: anyhow::Error) -> Self { - CliError::MonitoringError { reason: format!("{}", e) } + CliError::MonitoringError { + reason: format!("{}", e), + } } } impl From<()> for CliError { - fn from (v: ()) -> Self{ - return ().into() + fn from(v: ()) -> Self { + return ().into(); } } @@ -142,15 +143,100 @@ pub async fn connect_to_client() -> Result { // Returns an error if the command fails pub fn update_cli() { + let latest_version = get_latest_cfcli_version().expect("Can't get the latest version"); println!("{} {}", "=====>".blue().bold(), "Updating CortexFlow CLI"); - println!("{} {}", "=====>".blue().bold(), "Looking for a newer version"); + println!( + "{} {}", + "=====>".blue().bold(), + "Looking for a newer version \n " + ); + + let output = Command::new("cfcli") + .args(["--version"]) + .output() + .expect("error"); + + if !output.status.success() { + eprintln!( + "Error extracting the version : {}", + String::from_utf8_lossy(&output.stderr) + ); + } else { + // extract the cli version: + let version = String::from_utf8_lossy(&output.stdout) + .split_whitespace() + .last() + .expect("An error occured during the version extraction") + .to_string(); + + if version == latest_version { + println!( + "{} {} {} {} {} {}{}", + "=====>".blue().bold(), + "Your version".green().bold(), + (&version.to_string()).green().bold(), + "is already up to date".green().bold(), + "(latest:".green().bold(), + (&latest_version).green().bold(), + ")\n".green().bold() + ); + } else { + println!( + "{} {} {} {} {} {}{}", + "=====>".blue().bold(), + "Your version".red().bold(), + (&version.to_string()).red().bold(), + "needs to be updated".red().bold(), + "(latest:".red().bold(), + (&latest_version).red().bold(), + ")\n".red().bold() + ); + thread::sleep(Duration::from_secs(1)); + println!("{} {}", "=====>".blue().bold(), "Updating the CLI..."); + let update_command = Command::new("cargo") + .args(["install", "cortexflow-cli", "--force"]) + .output() + .expect("error"); + if !update_command.status.success() { + eprintln!( + "Error updating the CLI: {} ", + String::from_utf8_lossy(&update_command.stderr) + ); + } else { + println!( + "{} {}", + "=====>".blue().bold(), + "CLI updated".green().bold() + ) + } + } + } +} - let output = Command::new("cargo").args(["update", "cortexflow-cli"]).output().expect("error"); +// docs: +// +// This function returns the latest version of the CLI from the crates.io registry +pub fn get_latest_cfcli_version() -> Result { + let output = Command::new("cargo") + .args(["search", "cortexflow-cli", "--limit", "1"]) + .output() + .expect("Error"); if !output.status.success() { - eprintln!("Error updating CLI : {}", String::from_utf8_lossy(&output.stderr)); + return Err(Error::msg(format!( + "An error occured during the latest version extraction" + ))); } else { - println!("✅ Updated CLI"); + let command_stdout = String::from_utf8_lossy(&output.stdout); + + // here the data output have this structure: + // cortexflow-cli = "0.1.4" # CortexFlow command line interface made to interact with the CortexBrain core components + // ... and 3 crates more (use --limit N to see more) + + // i need to extract only the version tag + let version = command_stdout.split('"').nth(1).unwrap().to_string(); + + Ok(version) } } @@ -159,9 +245,24 @@ pub fn update_cli() { // This is a function to display the CLI Version,Author and Description using a fancy output style pub fn info() { - println!("{} {} {}", "=====>".blue().bold(), "Version:", env!("CARGO_PKG_VERSION")); - println!("{} {} {}", "=====>".blue().bold(), "Author:", env!("CARGO_PKG_AUTHORS")); - println!("{} {} {}", "=====>".blue().bold(), "Description:", env!("CARGO_PKG_DESCRIPTION")); + println!( + "{} {} {}", + "=====>".blue().bold(), + "Version:", + env!("CARGO_PKG_VERSION") + ); + println!( + "{} {} {}", + "=====>".blue().bold(), + "Author:", + env!("CARGO_PKG_AUTHORS") + ); + println!( + "{} {} {}", + "=====>".blue().bold(), + "Description:", + env!("CARGO_PKG_DESCRIPTION") + ); } // docs: @@ -210,18 +311,12 @@ pub async fn read_configs() -> Result, CliError> { Ok(Vec::new()) //in case the key fails } - Err(_) => { - Err( - CliError::ClientError( - kube::Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(_) => Err(CliError::ClientError(kube::Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } @@ -271,18 +366,12 @@ pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), } Ok(()) } - Err(_) => { - Err( - CliError::ClientError( - kube::Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(_) => Err(CliError::ClientError(kube::Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } @@ -350,21 +439,20 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), C let name = "cortexbrain-client-config"; let api: Api = Api::namespaced(client, namespace); - let blocklist_yaml = config_struct.blocklist + let blocklist_yaml = config_struct + .blocklist .iter() .map(|x| format!("{}", x)) .collect::>() .join("\n"); - let patch = Patch::Apply( - json!({ - "apiVersion": "v1", - "kind": "ConfigMap", - "data": { - "blocklist": blocklist_yaml - } - }) - ); + let patch = Patch::Apply(json!({ + "apiVersion": "v1", + "kind": "ConfigMap", + "data": { + "blocklist": blocklist_yaml + } + })); let patch_params = PatchParams::apply("cortexbrain").force(); match api.patch(name, &patch_params, &patch).await { @@ -379,17 +467,11 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), C Ok(()) } - Err(_) => { - Err( - CliError::ClientError( - kube::Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to connect to kubernetes client".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) - ) - ) - } + Err(_) => Err(CliError::ClientError(kube::Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to connect to kubernetes client".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }))), } } From 763720e26b59fd3c4ddba78904d935df286e803d Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Thu, 8 Jan 2026 01:09:01 +0100 Subject: [PATCH 4/4] [#167]: Added unit test to the the version extraction --- cli/src/essential.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 3f4ae3e..3f43350 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::process::Output; use std::thread; use std::time::Duration; @@ -234,12 +235,23 @@ pub fn get_latest_cfcli_version() -> Result { // ... and 3 crates more (use --limit N to see more) // i need to extract only the version tag - let version = command_stdout.split('"').nth(1).unwrap().to_string(); + let version = extract_version_from_output(command_stdout); Ok(version) } } +// docs: +// this is an helper function used in a unit test +// +// Takes a Clone-On-Write (Cow) smart pointer (the same type returned by the String::from_utf8_lossy(&output.stdout) code ) +// and returns a String that contains the cfcli version + +fn extract_version_from_output(command_stdout: Cow<'_, str>) -> String { + let version = command_stdout.split('"').nth(1).unwrap().to_string(); + version +} + // docs: // // This is a function to display the CLI Version,Author and Description using a fancy output style @@ -475,3 +487,20 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), C }))), } } + +#[cfg(test)] +mod tests { + use crate::essential::extract_version_from_output; + + #[test] + fn test_version_extraction() { + let command_stdout = String::from( + r#"cortexflow-cli = "0.1.4-test_123" + # CortexFlow command line interface made to interact with the CortexBrain core components... + and 3 crates more (use --limit N to see more)"#, + ); + + let extracted_command = extract_version_from_output(command_stdout.into()); + assert_eq!(extracted_command, "0.1.4-test_123"); + } +}