From 8f6a9249e5a66ca8899da452f56cdefc74b5861c Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Mon, 3 Nov 2025 11:56:08 +0100 Subject: [PATCH 01/13] [Code refactoring]: Updated return type for arg_parser function (prev: Result<(),Error> now: ()) --- cli/src/main.rs | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 77f1104..ada7b74 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -16,7 +16,7 @@ use std::result::Result::Ok; use std::string; use tracing::debug; -use crate::essential::{get_config_directory, get_startup_config_dir, info, read_configs, update_cli}; +use crate::essential::{info, read_configs, update_cli}; use crate::install::{InstallArgs, InstallCommands, install_cortexflow, install_simple_example}; use crate::logs::{LogsArgs, logs_command}; use crate::monitoring::{MonitorArgs, MonitorCommands, list_features, monitor_identity_events}; @@ -65,7 +65,7 @@ struct SetArgs { val: String, } -async fn args_parser() -> Result<(), Error> { +async fn args_parser(){ let args = Cli::parse(); let env = "kubernetes".to_string(); let general_data = GeneralData::new(env); @@ -73,91 +73,75 @@ async fn args_parser() -> Result<(), Error> { match args.cmd { Some(Commands::SetEnv(env)) => { general_data.set_env(env.val); - Ok(()) } Some(Commands::GetEnv) => { general_data.get_env_output(); - Ok(()) } Some(Commands::Install(installation_args)) => match installation_args.install_cmd { InstallCommands::All => { install_cortexflow().await; - Ok(()) } InstallCommands::TestPods => { install_simple_example(); - Ok(()) } }, Some(Commands::Uninstall) => { uninstall(); - Ok(()) } Some(Commands::Update) => { update_cli(); - Ok(()) } Some(Commands::Info) => { info(general_data); - Ok(()) } Some(Commands::Service(service_args)) => match service_args.service_cmd { ServiceCommands::List { namespace } => { Some(list_services(namespace)); - Ok(()) } ServiceCommands::Describe { service_name, namespace, } => { describe_service(service_name, &namespace); - Ok(()) } }, Some(Commands::Status(status_args)) => { status_command(status_args.output, status_args.namespace); - Ok(()) } Some(Commands::Logs(logs_args)) => { logs_command(logs_args.service, logs_args.component, logs_args.namespace); - Ok(()) } Some(Commands::Monitor(monitor_args)) => match monitor_args.monitor_cmd { MonitorCommands::List => { let _ = list_features().await; - Ok(()) } MonitorCommands::Connections => { let _ = monitor_identity_events().await; - Ok(()) } }, Some(Commands::Policies(policies_args)) => { match policies_args.policy_cmd { PoliciesCommands::CheckBlocklist => { let _ = check_blocklist().await; - Ok(()) } PoliciesCommands::CreateBlocklist => { // pass the ip as a monitoring flag match policies_args.flags { None => { println!("{}", "Insert at least one ip to create a blocklist".red()); - Ok(()) } - Some(exclude_flag) => { - println!("inserted ip: {} ", exclude_flag); + Some(ip) => { + println!("inserted ip: {} ", ip); //insert the ip in the blocklist - match create_blocklist(&exclude_flag).await { + match create_blocklist(&ip).await { Ok(_) => { //update the config metadata - let _ = update_config_metadata(&exclude_flag, "add").await; + let _ = update_config_metadata(&ip, "add").await; } Err(e) => { println!("{}", e); } } - Ok(()) } } } @@ -167,7 +151,6 @@ async fn args_parser() -> Result<(), Error> { "{}", "Insert at least one ip to remove from the blocklist".red() ); - Ok(()) } Some(ip) => { println!("Inserted ip: {}", ip); @@ -179,14 +162,12 @@ async fn args_parser() -> Result<(), Error> { println!("{}", e); } } - Ok(()) } }, } } None => { eprintln!("CLI unknown argument. Cli arguments passed: {:?}", args.cmd); - Ok(()) } } } From 4f2739d2bae6e76957f6c857d093ba0914583a22 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Tue, 4 Nov 2025 14:33:06 +0100 Subject: [PATCH 02/13] [Code refactoring]: removed serde yaml deprecated library --- cli/Cargo.lock | 2 -- cli/Cargo.toml | 1 - core/src/components/identity/Cargo.toml | 1 - 3 files changed, 4 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 67f71b3..3584cd8 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -394,7 +394,6 @@ dependencies = [ "prost", "prost-types", "serde", - "serde_yaml", "tokio", "tonic", "tonic-reflection", @@ -437,7 +436,6 @@ dependencies = [ "kube", "libc", "nix", - "serde_yaml", "tokio", "tracing", "tracing-subscriber", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1af552a..e5256f6 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -15,7 +15,6 @@ clap = { version = "4.5.38", features = ["derive"] } colored = "3.0.0" directories = "6.0.0" serde = { version = "1.0.219", features = ["derive"] } -serde_yaml = "0.9.34" tracing = "0.1.41" tokio = {version = "1.47.0",features = ["macros",'rt-multi-thread']} anyhow = "1.0.98" diff --git a/core/src/components/identity/Cargo.toml b/core/src/components/identity/Cargo.toml index dd0f401..d75242a 100644 --- a/core/src/components/identity/Cargo.toml +++ b/core/src/components/identity/Cargo.toml @@ -30,4 +30,3 @@ bytemuck_derive = "1.10.1" nix = { version = "0.30.1", features = ["net"] } kube = {version = "2.0.1",features = ["client"]} k8s-openapi = {version ="0.26.0", features = ["v1_34"]} -serde_yaml = "0.9.34" From e7ec18dfb142e938befef11f31539a878c597a08 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Tue, 4 Nov 2025 14:38:16 +0100 Subject: [PATCH 03/13] [Code refactoring]: Added installation types, Added custom Error type, Added functions detailed documentation, removed duplicated functions. Added connect_to_client aux function to connect to kubernetes client --- cli/src/essential.rs | 152 +++++++-------- cli/src/install.rs | 449 ++++++++++++++++++++++++++++--------------- 2 files changed, 363 insertions(+), 238 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 37f0e5d..93f5783 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -21,68 +21,76 @@ use std::process::Command; use kube::api::{Api, ObjectMeta, Patch, PatchParams, PostParams}; use kube::client::Client; -pub struct GeneralData { - env: String, -} +//pub struct GeneralData { +// env: String, +//} #[derive(Serialize)] pub struct MetadataConfigFile { blocklist: Vec, } -#[derive(Debug)] -pub enum Environments { - Kubernetes, -} -impl TryFrom<&str> for Environments { - type Error = String; - - fn try_from(environment: &str) -> Result { - match environment { - "kubernetes" | "k8s" => Ok(Environments::Kubernetes), - _ => - Err( - format!("Environment '{}' not supported. Please insert a supported value: Kubernetes, K8s", environment) - ), - } - } -} - -//for owned types -impl TryFrom for Environments { - type Error = String; - fn try_from(environment: String) -> Result { - Environments::try_from(environment.as_str()) - } -} +//FIXME: remove this part +//#[derive(Debug)] +//pub enum Environments { +// Kubernetes, +//} +//impl TryFrom<&str> for Environments { +// type Error = String; +// +// fn try_from(environment: &str) -> Result { +// match environment { +// "kubernetes" | "k8s" => Ok(Environments::Kubernetes), +// _ => +// Err( +// format!("Environment '{}' not supported. Please insert a supported value: Kubernetes, K8s", environment) +// ), +// } +// } +//} -impl Environments { - pub fn base_command(&self) -> &'static str { - match self { - Environments::Kubernetes => "kubectl", - } - } +//for owned types +//impl TryFrom for Environments { +// type Error = String; +// +// fn try_from(environment: String) -> Result { +// Environments::try_from(environment.as_str()) +// } +//} + +//impl Environments { +// pub fn base_command(&self) -> &'static str { +// match self { +// Environments::Kubernetes => "kubectl", +// } +// } +//} + +//impl GeneralData { + //pub const VERSION: &str = env!("CARGO_PKG_VERSION"); + //pub const AUTHOR: &str = env!("CARGO_PKG_AUTHORS"); + //pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); + + //pub fn new(env: String) -> Self { + // GeneralData { + // env: env.to_string(), // FIXME: remove this field + // } + //} + //pub fn set_env(mut self, env: String) { + // self.env = env; + //} + //pub fn get_env(self) -> String { + // self.env + //} + //pub fn get_env_output(self) { + // println!("{:?}", self.env) + //} +//} + +pub async fn connect_to_client() -> Result { + let client = Client::try_default().await; + client } -impl GeneralData { - pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - pub const AUTHOR: &str = env!("CARGO_PKG_AUTHORS"); - pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); - - pub fn new(env: String) -> Self { - GeneralData { - env: env.to_string(), - } - } - pub fn set_env(mut self, env: String) { - self.env = env; - } - pub fn get_env(self) -> String { - self.env - } - pub fn get_env_output(self) { - println!("{:?}", self.env) - } -} pub fn update_cli() { println!("{} {}", "=====>".blue().bold(), "Updating CortexFlow CLI"); @@ -96,16 +104,12 @@ pub fn update_cli() { println!("āœ… Updated CLI"); } } -pub fn info(general_data: GeneralData) { - println!("{} {} {}", "=====>".blue().bold(), "Version:", GeneralData::VERSION); - println!("{} {} {}", "=====>".blue().bold(), "Author:", GeneralData::AUTHOR); - println!("{} {} {}", "=====>".blue().bold(), "Description:", GeneralData::DESCRIPTION); - println!("{} {} {}", "=====>".blue().bold(), "Environment:", general_data.get_env()); -} - -fn is_supported_env(env: &str) -> bool { - matches!(env.to_lowercase().trim(), "kubernetes" | "k8s") -} +//pub fn info(general_data: GeneralData) { +// println!("{} {} {}", "=====>".blue().bold(), "Version:", GeneralData::VERSION); +// println!("{} {} {}", "=====>".blue().bold(), "Author:", GeneralData::AUTHOR); +// println!("{} {} {}", "=====>".blue().bold(), "Description:", GeneralData::DESCRIPTION); +// println!("{} {} {}", "=====>".blue().bold(), "Environment:", general_data.get_env()); // FIXME: remove this field +//} pub fn create_configs() -> MetadataConfigFile { let mut blocklist: Vec = Vec::new(); @@ -229,23 +233,3 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), a Ok(()) } - -//TODO: add here an explanation of what are config_dir and file_path -pub fn get_config_directory() -> Result<(PathBuf, PathBuf), ()> { - let dirs = ProjectDirs::from("org", "cortexflow", "cfcli").expect( - "Cannot determine the config directory" - ); - let config_dir = dirs.config_dir().to_path_buf(); - let file_path = config_dir.join("config.yaml"); - - Ok((config_dir, file_path)) -} - -pub fn get_startup_config_dir() -> bool { - ProjectDirs::from("org", "cortexflow", "cfcli") - .map(|dirs| { - let path = dirs.config_dir(); - path.exists() - }) - .unwrap_or(false) -} diff --git a/cli/src/install.rs b/cli/src/install.rs index af0ae48..1a603c9 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -1,17 +1,63 @@ -use std::process::{ Command, exit }; - -use crate::essential::Environments; -use crate::essential::{ create_config_file, create_configs, get_config_directory, read_configs }; - use colored::Colorize; -use std::thread; -use std::time::Duration; +use kube::Client; use tracing::debug; +use clap::{ Args, Subcommand, command }; +use std::{ process::{ Command, exit }, fmt, thread, time::Duration }; +use crate::{ + essential::{ connect_to_client, create_config_file, create_configs, read_configs }, + install, +}; + +static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command + +// docs: +// +// Custom error definition +// InstallerError: +// - used for general installation errors occured during the installation of cortexflow components. Can be used for: +// - Return downloading errors +// - Return unsuccessful file removal +// +// +// implements fmt::Display for user-friendly error messages +// + +#[derive(Debug, Clone)] +struct InstallerError { + reason: String, +} + +impl fmt::Display for InstallerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "An error occured while installing cortexflow components. Reason: {}", + self.reason + ); + Ok(()) + } +} + +// docs: +// +// Custom enum definition: +// InstallationType: +// - used to pass installation files. Can be used for: +// - Install components by passing a Vec containing the components urls +// - Install a simple-example by passing the component url (String) +// +// -use clap::command; -use clap::{ Args, Subcommand }; +enum InstallationType { + Components(Vec), + SimpleExample(String), +} + +// docs: +// +// main cortexflow installation function to install all the cortexflow components: +// This function creates the cortexflow namespace, manages the metadata file creation and removes the temporary installation files -//install subcommands #[derive(Subcommand, Debug, Clone)] pub enum InstallCommands { #[command(name = "cortexflow", about = "Install all the CortexBrain core components")] @@ -30,205 +76,299 @@ pub struct InstallArgs { pub install_cmd: InstallCommands, } -/* components installation function */ -fn install_cluster_components(env: String) { - let user_env = Environments::try_from(env.to_lowercase()); - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); +// docs: +// +// main cortexflow installation function to install all the cortexflow components: +// This function creates the cortexflow namespace, manages the metadata file creation and removes the temporary installation files + +pub async fn install_cortexflow() { + 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() + .expect("Failed to create cortexflow namespace"); + + let metadata_configs = create_configs(); + create_config_file(metadata_configs).await; + install_cluster_components(); +} + +// docs: +// +// main cortexflow installation function to install the examples: +// This function installs the demostration examples + +pub fn install_simple_example() { + println!("{} {}", "=====>".blue().bold(), "Installing simple example".white()); + install_simple_example_component(); +} + +//docs: +// +// This function manages the installation of the cortexflow cluster components +// Steps: +// - Connects to kubernetes client +// - Copies installation files from the offcial github repository +// - Executes the install_components function +// - Executes the rm_installation_files to remove the temporary installation files +// +// Returns an InstallerError if something fails + +async fn install_cluster_components() -> Result<(), InstallerError> { + match connect_to_client().await { + Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); - copy_installation_files(); + 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() + ] + ) + )?; thread::sleep(Duration::from_secs(1)); - install_components(env.to_string()); + install_components("cortexbrain")?; println!("\n"); - rm_installation_files(); + 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()); + Ok(()) } Err(e) => { - eprintln!("An error occured while installing cortexflow components: {:?}", e); - exit(1) + return Err(InstallerError { reason: "Can't connect to kubernetes client".to_string() }); } } } -/* example installation function */ -fn install_simple_example_component(env: String) { - let user_env = Environments::try_from(env.to_lowercase()); - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); +//docs: +// +// This function manages the installation of the examples +// Steps: +// - Connects to kubernetes client +// - Copies examples files from the offcial github repository +// - Executes the install_example function +// - Executes the rm_example_installation_file to remove the temporary installation files +// +// Returns an InstallerError if something fails + +async fn install_simple_example_component() -> Result<(), InstallerError> { + match connect_to_client().await { + Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); - copy_example_installation_file(); + 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() + ) + )?; thread::sleep(Duration::from_secs(1)); - install_example(env.to_string()); + install_components("simple-example")?; println!("\n"); - rm_example_installation_file(); + rm_installation_files( + InstallationType::SimpleExample("deploy-test-pod.yaml".to_string()) + )?; println!("{} {}", "=====>".blue().bold(), "installation completed".white()); + Ok(()) } Err(e) => { - eprintln!("An error occured while installing cortexflow components: {:?}", e); - exit(1) + return Err(InstallerError { reason: "Can't connect to kubernetes client".to_string() }); } } } -/* main installation function */ -pub async fn install_cortexflow() { - 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() - .expect("Failed to create cortexflow namespace"); +//docs: +// +// This is an auxiliary function to help manage the cortexflow components during the installation +// Steps: +// - Read the Vec<&str> with the list of components to install +// - Executes the apply_component function +// - let metadata_configs = create_configs(); - create_config_file(metadata_configs).await; +fn install_components(components_type: &str) -> Result<(), InstallerError> { + if components_type == "cortexbrain" { + let files_to_install = vec![ + "configmap-role.yaml", + "rolebinding.yaml", + "cortexflow-rolebinding.yaml", + "identity.yaml", + "agent.yaml" + ]; + let tot_files = files_to_install.len(); - let env = "kubernetes".to_string(); - install_cluster_components(env); -} -/* install simple example */ -pub fn install_simple_example() { - println!("{} {}", "=====>".blue().bold(), "Installing simple example".white()); - - let file_path = get_config_directory().unwrap().1; + println!("{} {}", "=====>".blue().bold(), "Installing cortexflow components".white()); + let mut i = 1; - let env = "kubectl".to_string(); - install_simple_example_component(env); -} + for component in files_to_install { + println!( + "{} {}{}{}{} {} {} {}", + "=====>".blue().bold(), + "(", + i, + "/", + tot_files, + ")", + "Applying ", + component + ); + apply_component(component); + i = i + 1; + } + } else if components_type == "simple-example" { + let files_to_install = vec!["deploy-test-pod.yaml"]; + let tot_files = files_to_install.len(); + let mut i = 1; -/* install example component */ -fn install_example(env: String) { - let files_to_install = vec!["deploy-test-pod.yaml"]; - let tot_files = files_to_install.len(); - - println!("{} {}", "=====>".blue().bold(), "Installing cortexflow components".white()); - let user_env = env.as_str(); - debug!("Debugging env var in install components {:?}", user_env); - - let mut i = 1; - - for component in files_to_install { - println!( - "{} {}{}{}{} {} {} {}", - "=====>".blue().bold(), - "(", - i, - "/", - tot_files, - ")", - "Applying ", - component - ); - apply_component(component, user_env); - i = i + 1; + for component in files_to_install { + println!( + "{} {}{}{}{} {} {} {}", + "=====>".blue().bold(), + "(", + i, + "/", + tot_files, + ")", + "Applying ", + component + ); + apply_component(component); + i = i + 1; + } + } else { + return Err(InstallerError { + reason: "An error occured: No installation type selected".to_string(), + }); } + Ok(()) } -/* Installation functions */ -fn install_components(env: String) { - let files_to_install = vec![ - "configmap-role.yaml", - "rolebinding.yaml", - "cortexflow-rolebinding.yaml", - "identity.yaml", - "agent.yaml" - ]; - let tot_files = files_to_install.len(); - - println!("{} {}", "=====>".blue().bold(), "Installing cortexflow components".white()); - let user_env = env.as_str(); - debug!("Debugging env var in install components {:?}", user_env); - - let mut i = 1; - - for component in files_to_install { - println!( - "{} {}{}{}{} {} {} {}", - "=====>".blue().bold(), - "(", - i, - "/", - tot_files, - ")", - "Applying ", - component - ); - apply_component(component, user_env); - i = i + 1; - } -} +//docs: +// +// This is an auxiliary function to help manage the cortexflow components during the installation +// Steps: +// - Read the file name of a kubernetes manifest (e.g agent.yaml) +// - Applies the manifest using the command kubectl apply -f +// +// Returns an InstallerError if something fails -fn apply_component(file: &str, env: &str) { - let output = Command::new(env) +fn apply_component(file: &str) -> Result<(), InstallerError> { + let output = Command::new(BASE_COMMAND) .args(["apply", "-f", file]) .output() - .expect("cannot install component from file"); + .map_err(|_| InstallerError { reason: "Can't install component from file".to_string() })?; if !output.status.success() { eprintln!("Error installing file: {}:\n{}", file, String::from_utf8_lossy(&output.stderr)); } else { println!("āœ… Applied {}", file); } - thread::sleep(Duration::from_secs(2)); + Ok(()) } -fn copy_installation_files() { - download_file( - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/configmap-role.yaml" - ); - download_file( - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/rolebinding.yaml" - ); - download_file( - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/main/core/src/testing/cortexflow-rolebinding.yaml" - ); - download_file( - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/feature/ebpf-core/core/src/testing/identity.yaml" - ); - download_file( - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/feature/ebpf-core/core/src/testing/agent.yaml" - ); - println!("\n"); -} -fn copy_example_installation_file() { - download_file( - "https://raw.githubusercontent.com/CortexFlow/CortexBrain/refs/heads/feature/ebpf-core/core/src/testing/deploy-test-pod.yaml" - ); +//docs: +// +// This is an auxiliary function to download all the installation files +// Steps: +// - Read the Vec containing the file names of the installation files from the InstallationType enum +// - Download the corresponding installation files from the github repository +// +// Returns an InstallerError if something fails + +fn download_installation_files(installation_files: InstallationType) -> Result<(), InstallerError> { + match installation_files { + InstallationType::Components(files) => { + for src in files.iter() { + download_file(&src)?; + } + } + InstallationType::SimpleExample(file) => { + download_file(&file)?; + } + } println!("\n"); + Ok(()) } -fn rm_installation_files() { - println!("{} {}", "=====>".blue().bold(), "Removing temporary installation files".white()); - rm_file("configmap-role.yaml"); - rm_file("rolebinding.yaml"); - rm_file("cortexflow-rolebinding.yaml"); - rm_file("identity.yaml"); - rm_file("agent.yaml"); -} -fn rm_example_installation_file() { + +//docs: +// +// This is an auxiliary function to specifically remove the installation files after the installation +// Steps: +// - Read the Vec containing the file names of the installation files from the InstallationType enum +// - Executes the rm_file function for each installation file +// +// Returns an InstallerError if something fails + +fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), InstallerError> { println!("{} {}", "=====>".blue().bold(), "Removing temporary installation files".white()); - rm_file("deploy-test-pod.yaml"); + match file_to_remove { + InstallationType::Components(files) => { + for src in files.iter() { + rm_file(&src)?; + } + } + InstallationType::SimpleExample(file) => { + rm_file(&file)?; + } + } + + Ok(()) } -/* Auxiliary functions */ -fn download_file(src: &str) { - let output = Command::new("wget").args([src]).output().expect("cannot import config file"); +//docs: +// +// This is an auxiliary function to help manage the cortexflow components during the installation +// Steps: +// - Read the url name of a kubernetes manifest +// - Download the manifest file from the cortexflow repository +// +// Returns a InstallerError if something fails + +fn download_file(src: &str) -> Result<(), InstallerError> { + let output = Command::new("wget") + .args([src]) + .output() + .map_err(|_| 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)); } else { println!("āœ… Copied file from {} ", src); } - thread::sleep(Duration::from_secs(2)); + Ok(()) } -fn rm_file(file_to_remove: &str) { + +//docs: +// +// This is an auxiliary function to help manage the cortexflow components during the installation +// Steps: +// - Read the file name +// - Removes the file using the rm -f +// +// Returns an InstallerError if something fails + +fn rm_file(file_to_remove: &str) -> Result<(), InstallerError> { let output = Command::new("rm") .args(["-f", file_to_remove]) .output() - .expect("cannot remove temporary installation file"); + .map_err(|_| InstallerError { + reason: "cannot remove temporary installation file".to_string(), + })?; if !output.status.success() { eprintln!( @@ -241,4 +381,5 @@ fn rm_file(file_to_remove: &str) { } thread::sleep(Duration::from_secs(2)); + Ok(()) } From 7e302a1eb65d580604adc88e24a4197b6f1ccef1 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Tue, 4 Nov 2025 21:08:50 +0100 Subject: [PATCH 04/13] [Code refactoring]: moved BASE_COMMAND static var in essential.rs module, moved connect_to_client function to essential.rs. Code refactoring of logs.r: added docs, improved error handling --- cli/src/essential.rs | 2 + cli/src/install.rs | 3 +- cli/src/logs.rs | 457 +++++++++++++++++++++++++------------------ 3 files changed, 266 insertions(+), 196 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 93f5783..1c08a2f 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -21,6 +21,8 @@ use std::process::Command; use kube::api::{Api, ObjectMeta, Patch, PatchParams, PostParams}; use kube::client::Client; +pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command + //pub struct GeneralData { // env: String, //} diff --git a/cli/src/install.rs b/cli/src/install.rs index 1a603c9..af58d26 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -4,11 +4,10 @@ use tracing::debug; use clap::{ Args, Subcommand, command }; use std::{ process::{ Command, exit }, fmt, thread, time::Duration }; use crate::{ - essential::{ connect_to_client, create_config_file, create_configs, read_configs }, + essential::{ connect_to_client, create_config_file, create_configs, read_configs,BASE_COMMAND }, install, }; -static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command // docs: // diff --git a/cli/src/logs.rs b/cli/src/logs.rs index 2e760d3..c0abc4e 100644 --- a/cli/src/logs.rs +++ b/cli/src/logs.rs @@ -1,12 +1,8 @@ -use std::str; -use std::process::Command; - +use std::{ str, process::Command, result::Result::Ok }; use colored::Colorize; - -use crate::essential::{Environments, get_config_directory, read_configs}; - use clap::Args; - +use kube::{ Error, core::ErrorResponse }; +use crate::essential::{ connect_to_client, BASE_COMMAND }; #[derive(Args, Debug, Clone)] pub struct LogsArgs { @@ -18,7 +14,6 @@ pub struct LogsArgs { pub namespace: Option, } - #[derive(Debug, Clone)] pub enum Component { ControlPlane, @@ -45,38 +40,172 @@ impl Component { } } -fn check_namespace_exists(namespace: &str) -> bool { - let file_path = get_config_directory().unwrap().1; +// docs: +// +// This is the main function for the logs command +// Steps: +// - connects to kubernetes client +// - returns the list of namespaces in Vec format +// +// +// Returns a kube::Error if the connectiion to the kubeapi fails + +pub async fn logs_command( + service: Option, + component: Option, + namespace: Option +) -> Result<(), kube::Error> { + match connect_to_client().await { + Ok(_) => { + let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); + if !check_namespace_exists(&ns).await? { + let available_namespaces = get_available_namespaces().await?; + println!("\nāŒ Namespace '{}' not found", ns); + println!("{}", "=".repeat(50)); + if !available_namespaces.is_empty() { + println!("\nšŸ“‹ Available namespaces:"); + for available_ns in &available_namespaces { + println!(" • {}", available_ns); + } + } else { + println!("No namespaces found in the cluster."); + } + std::process::exit(1); + } - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) - .args(["get", "namespace", namespace]) - .output(); + let pods = match (service, component) { + (Some(service_name), Some(component_str)) => { + let comp = Component::from(component_str); + println!( + "{} Getting logs for service '{}' with component '{:?}' in namespace '{}'", + "=====>".blue().bold(), + service_name, + comp, + ns + ); + let service_pods = get_pods_for_service(&ns, &service_name).await?; + let component_pods = get_pods_for_component(&ns, &comp).await?; + service_pods + .into_iter() + .filter(|pod| component_pods.contains(pod)) + .collect() + } + (Some(service_name), None) => { + println!("Getting logs for service '{}' in namespace '{}'", service_name, ns); + get_pods_for_service(&ns, &service_name).await? + } + (None, Some(component_str)) => { + let comp = Component::from(component_str); + println!("Getting logs for component '{:?}' in namespace '{}'", comp, ns); + get_pods_for_component(&ns, &comp).await? + } + (None, None) => { + println!( + "{} Getting logs for all pods in namespace '{}'", + "=====>".blue().bold(), + ns + ); + get_all_pods(&ns).await? + } + }; - match output { - Ok(output) => output.status.success(), - Err(_) => false, + if pods.is_empty() { + println!("No pods found matching the specified criteria"); + return Ok(()); } + + for pod in pods { + println!("{} Logs for pod: {:?}", "=====>".blue().bold(), pod); + match + Command::new(BASE_COMMAND).args(["logs", &pod, "-n", &ns, "--tail=50"]).output() + { + Ok(output) => { + if output.status.success() { + let stdout = str::from_utf8(&output.stdout).unwrap_or(""); + if stdout.trim().is_empty() { + println!("No logs available for pod '{:?}'", pod); + } else { + println!("{}", stdout); + } + } else { + let stderr = str::from_utf8(&output.stderr).unwrap_or("Unknown error"); + eprintln!("Error getting logs for pod '{:?}': {}", pod, stderr); + } + } + Err(err) => { + eprintln!( + "Failed to execute {} logs for pod '{:?}': {}", + BASE_COMMAND, + pod, + err + ); + } + } + } + + Ok(()) + } + Err(_) => { + Err( + 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(_) => false, } } -fn get_available_namespaces() -> Vec { - let file_path = get_config_directory().unwrap().1; +// docs: +// +// This is an auxiliary function used in the logs_command +// Steps: +// - connects to kubernetes client +// - returns true if the namespace exists or false if the namespace doesn't exists +// +// +// Returns a kube::Error if the connection fails + +async fn check_namespace_exists(namespace: &str) -> Result { + match connect_to_client().await { + Ok(_) => { + let output = Command::new(BASE_COMMAND).args(["get", "namespace", namespace]).output(); - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); + match output { + Ok(output) => Ok(output.status.success()), + Err(_) => Ok(false), + } + } + Err(_) => { + return Err( + 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, + }) + ); + } + } +} - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) +// docs: +// +// This function returns the available namespaces: +// Steps: +// - connects to kubernetes client +// - returns the list of namespaces in Vec format +// +// +// Returns a kube::Error if the connectiion to the kubeapi fails + +async fn get_available_namespaces() -> Result, kube::Error> { + match connect_to_client().await { + Ok(_) => { + let output = Command::new(BASE_COMMAND) .args([ "get", "namespaces", @@ -89,29 +218,46 @@ fn get_available_namespaces() -> Vec { match output { Ok(output) if output.status.success() => { let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout + let ns = stdout .lines() .map(|line| line.trim().to_string()) .filter(|line| !line.is_empty()) - .collect() + .collect(); + Ok(ns) } - _ => Vec::new(), + _ => Ok(Vec::new()), } } - Err(_) => Vec::new(), + Err(_) => { + return Err( + 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, + }) + ); + } } } -fn get_pods_for_service(namespace: &str, service_name: &str) -> Vec { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) +// docs: +// +// This function returns the pods: +// Steps: +// - connects to kubernetes client +// - returns the list of pods associated with a kubernetes service filtering by labels in Vec format +// +// +// Returns a kube::Error if the connectiion to the kubeapi fails + +async fn get_pods_for_service( + namespace: &str, + service_name: &str +) -> Result, kube::Error> { + match connect_to_client().await { + Ok(_) => { + let output = Command::new(BASE_COMMAND) .args([ "get", "pods", @@ -128,29 +274,47 @@ fn get_pods_for_service(namespace: &str, service_name: &str) -> Vec { match output { Ok(output) if output.status.success() => { let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout + let pods = stdout .lines() .map(|line| line.trim().to_string()) .filter(|line| !line.is_empty()) - .collect() + .collect(); + Ok(pods) } - _ => Vec::new(), + _ => Ok(Vec::new()), } } - Err(_) => Vec::new(), + Err(_) => { + return Err( + 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, + }) + ); + } } } -fn get_pods_for_component(namespace: &str, component: &Component) -> Vec { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) +// docs: +// +// This function returns the pods: +// Steps: +// - connects to kubernetes client +// - returns the list of pods associated with a componet object to dynamically construct the +// label selector,in Vec format +// +// +// Returns a kube::Error if the connectiion to the kubeapi fails + +async fn get_pods_for_component( + namespace: &str, + component: &Component +) -> Result, kube::Error> { + match connect_to_client().await { + Ok(_) => { + let output = Command::new(BASE_COMMAND) .args([ "get", "pods", @@ -167,29 +331,43 @@ fn get_pods_for_component(namespace: &str, component: &Component) -> Vec match output { Ok(output) if output.status.success() => { let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout + let pods = stdout .lines() .map(|line| line.trim().to_string()) .filter(|line| !line.is_empty()) - .collect() + .collect(); + Ok(pods) } - _ => Vec::new(), + _ => Ok(Vec::new()), } } - Err(_) => Vec::new(), + Err(_) => { + return Err( + 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, + }) + ); + } } } -fn get_all_pods(namespace: &str) -> Vec { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) +// docs: +// +// This function returns the available namespaces: +// Steps: +// - connects to kubernetes client +// - returns the list of all pods in Vec format +// +// +// Returns a kube::Error if the connectiion to the kubeapi fails + +async fn get_all_pods(namespace: &str) -> Result, kube::Error> { + match connect_to_client().await { + Ok(_) => { + let output = Command::new(BASE_COMMAND) .args([ "get", "pods", @@ -204,134 +382,25 @@ fn get_all_pods(namespace: &str) -> Vec { match output { Ok(output) if output.status.success() => { let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout + let pods = stdout .lines() .map(|line| line.trim().to_string()) .filter(|line| !line.is_empty()) - .collect() + .collect(); + Ok(pods) } - _ => Vec::new(), + _ => Ok(Vec::new()), } } - Err(_) => Vec::new(), - } -} - -pub fn logs_command(service: Option, component: Option, namespace: Option) { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); - - // namespace check - if !check_namespace_exists(&ns) { - let available_namespaces = get_available_namespaces(); - - println!("\nāŒ Namespace '{}' not found", ns); - println!("{}", "=".repeat(50)); - - if !available_namespaces.is_empty() { - println!("\nšŸ“‹ Available namespaces:"); - for available_ns in &available_namespaces { - println!(" • {}", available_ns); - } - } else { - println!("No namespaces found in the cluster."); - } - - std::process::exit(1); - } - - // determine pods. - let pods = match (service, component) { - (Some(service_name), Some(component_str)) => { - let comp = Component::from(component_str); - println!( - "{} {} {} {} {} {:?} {}", - "=====>".blue().bold(), - "Getting logs for service", - "with component ", - "in namespace", - service_name, - comp, - ns - ); - - let service_pods = get_pods_for_service(&ns, &service_name); - let component_pods = get_pods_for_component(&ns, &comp); - - // intersection - service_pods - .into_iter() - .filter(|pod| component_pods.contains(pod)) - .collect() - } - (Some(service_name), None) => { - //only service - println!( - "Getting logs for service '{}' in namespace '{}'", - service_name, ns - ); - get_pods_for_service(&ns, &service_name) - } - (None, Some(component_str)) => { - //only component - let comp = Component::from(component_str); - println!( - "Getting logs for component '{:?}' in namespace '{}'", - comp, ns - ); - get_pods_for_component(&ns, &comp) - } - (None, None) => { - //neither, get all - println!( - "{} {} {}", - "=====>".blue().bold(), - "Getting logs for all pods in namespace ", - ns - ); - get_all_pods(&ns) - } - }; - - if pods.is_empty() { - println!("No pods found matching the specified criteria"); - return; - } - - for pod in pods { - println!("{} {} {}", "=====>".blue().bold(), "Logs for pod: ", pod); - - let output = Command::new(env) - .args(["logs", &pod, "-n", &ns, "--tail=50"]) - .output(); - - match output { - Ok(output) => { - if output.status.success() { - let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - if stdout.trim().is_empty() { - println!("No logs available for pod '{}'", pod); - } else { - println!("{}", stdout); - } - } else { - let stderr = str::from_utf8(&output.stderr).unwrap_or("Unknown error"); - eprintln!("Error getting logs for pod '{}': {}", pod, stderr); - } - } - Err(err) => { - eprintln!("Failed to execute {} logs for pod '{}': {}", env, pod, err); - } - } - } + Err(_) => { + return Err( + 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) => eprintln!("An error occured while returning the cluster environment: {:?}",e), } } From 9682dc3ac37f3d7286f7a6d2ffa0de168747813e Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Thu, 6 Nov 2025 22:24:53 +0100 Subject: [PATCH 05/13] [Code refactoring]: refactored uninstall commands. Added better error handling, Added documentation to every function --- cli/src/uninstall.rs | 246 +++++++++++++++++++++++++------------------ 1 file changed, 143 insertions(+), 103 deletions(-) diff --git a/cli/src/uninstall.rs b/cli/src/uninstall.rs index 91e6329..0e55594 100644 --- a/cli/src/uninstall.rs +++ b/cli/src/uninstall.rs @@ -1,127 +1,167 @@ -use crate::essential::{Environments, get_config_directory, read_configs}; +use crate::{ essential::read_configs, status }; use colored::Colorize; -use std::io::stdin; -use std::process::Command; +use std::{ io::stdin, process::Command, time::Duration, thread }; use tracing::debug; -use std::thread; -use std::time::Duration; - -pub fn uninstall() { - //let file_path = get_config_directory().unwrap().1; - //let dir_config_path = get_config_directory().unwrap().0; - //debug!("file_path variable:{:?}", dir_config_path); - //let env_from_file = read_configs(file_path.clone()); - //let user_env = Environments::try_from(env_from_file.to_lowercase()); - - //match user_env { - // Ok(cluster_environment) => { - let env = "kubectl".to_string(); - 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"); - - let trimmed_input = userinput.trim(); - if trimmed_input == "1" { - uninstall_all(&env); - println!( - "{} {}", - "=====>".blue().bold(), - "Do you want to remove the command line metadata? [y/n]" - ); +use crate::essential::{ BASE_COMMAND, InstallationError, connect_to_client }; +use kube::{ Error, core::ErrorResponse }; + +//docs: +// +// This function manages the uninstall process for the cortexflow components +// Steps: +// - connects to kubernetes client +// - display the uninstall options +// - read the user input (e.g. 1 > all components) +// - uninstall the selected component or the whole namespace +// +// Returns an InstallationError if something fails + +pub async fn uninstall() -> Result<(), InstallationError> { + match connect_to_client().await { + Ok(_) => { + 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"); - //clear the user input before assigning a new value - userinput.clear(); - stdin() - .read_line(&mut userinput) - .expect("Error reading user input"); - - if userinput.trim() == "y" { - println!( - "{} {}", - "=====>".blue().bold(), - "Deleting metadata config files" - ); - //println!( - // "{} {}: {:?}", - // "=====>".blue().bold(), - // "Removing", - // dir_config_path.clone() - //); - //rm_dir(dir_config_path.as_os_str().to_str().unwrap()); - } else if userinput.trim() == "n" { - println!( - "{} {}", - "=====>".blue().bold(), - "Skipping metadata config files deletion" - ); + let trimmed_input = userinput.trim(); + if trimmed_input == "1" { + uninstall_all().await?; + } else if trimmed_input == "2" { + uninstall_component("deployment", "cortexflow-identity").await?; + } + Ok(()) + } + Err(_) => { + Err( + InstallationError::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, + }) + ) + ) } - } else if trimmed_input == "2" { - uninstall_component("deployment", "cortexflow-identity", &env.to_owned()); } } -// Err(e) => println!("An error occured while reading the config files: {}", e), -//} -//} + +//docs: +// +// This function only print the uninstall options fn display_uninstall_options() { println!("{} {}", "=====>".blue().bold(), "1 > all"); println!("{} {}", "=====>".blue().bold(), "2 > identity-service"); } -fn uninstall_all(env: &str) { - println!( - "{} {}", - "=====>".blue().bold(), - "Deleting cortexflow components".red() - ); - //uninstall_component("namespace", "cortexflow", env); - let output = Command::new(env) - .args(["delete", "namespace", "cortexflow"]) - .output() - .expect("Error deleting cortexflow namespace"); +//docs: +// +// This function manages the uninstall of the whole cortexflow namespace +// Steps: +// - connects to kubernetes client +// - execute the command to uninstall the cortexflow namespace +// +// Returns an InstallationError if something fails - if !output.status.success() { - eprintln!( - "Error deleting cortexflow namespace:\n{}", - String::from_utf8_lossy(&output.stderr) - ); - } else { - println!("āœ… Removed cortexflow namespace"); +async fn uninstall_all() -> Result<(), InstallationError> { + match connect_to_client().await { + Ok(_) => { + println!("{} {}", "=====>".blue().bold(), "Deleting cortexflow components".red()); + let output = Command::new(BASE_COMMAND) + .args(["delete", "namespace", "cortexflow"]) + .output() + .map_err(|e| InstallationError::InstallerError { + reason: format!("Failed to execute delete command: {}", e), + })?; + + if output.status.success() { + println!("āœ… Removed cortexflow namespace"); + Ok(()) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("Error deleting cortexflow namespace. Error: {} ", stderr); + Err(InstallationError::InstallerError { + reason: format!("Failed to delete cortexflow namespace. Error: {}", stderr), + }) + } + } + Err(_) => { + Err( + InstallationError::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, + }) + ) + ) + } } } -fn uninstall_component(component_type: &str, component: &str, env: &str) { - println!( - "{} {} {}", - "=====>".blue().bold(), - "Deleting service", - component - ); - let output = Command::new(env) - .args(["delete", component_type, component, "-n", "cortexflow"]) - .output() - .expect("Error deleting cortexflow-identity"); +//docs: +// +// This function manages the uninstall of given cortexflow components +// Steps: +// - connects to kubernetes client +// - executes the command to uninstall a given component +// +// Returns an InstallerError if something fails - if !output.status.success() { - eprintln!( - "Error deleting: {}:\n{}", - component, - String::from_utf8_lossy(&output.stderr) - ); - } else { - println!("āœ… Removed component {}", component); +async fn uninstall_component( + component_type: &str, + component: &str +) -> Result<(), InstallationError> { + match connect_to_client().await { + Ok(_) => { + println!("{} {} {}", "=====>".blue().bold(), "Deleting service", component); + + let output = Command::new(BASE_COMMAND) + .args(["delete", component_type, component, "-n", "cortexflow"]) + .output() + .map_err(|e| InstallationError::InstallerError { + reason: format!("Failed to execute delete command: {}", e), + })?; + + if output.status.success() { + println!("āœ… Removed component {}", component); + Ok(()) + } else { + let stderr = String::from_utf8_lossy(&output.stderr); + eprintln!("Error deleting {}:\n{}", component, stderr); + Err(InstallationError::InstallerError { + reason: format!("Failed to delete component '{}': {}", component, stderr), + }) + } + } + Err(_) => { + Err( + InstallationError::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, + }) + ) + ) + } } } +// +// +//docs: +// +// This function is deprecated and will be removed in the next version +// +// Do not include or refactor this function +#[deprecated(since = "0.1.4")] fn rm_dir(directory_to_remove: &str) { let output = Command::new("rm") .args(["-rf", directory_to_remove]) From 8d8b2d2e1af8b53721627ac4b2f7d0b85a4e208c Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Thu, 6 Nov 2025 22:28:55 +0100 Subject: [PATCH 06/13] [Code refactoring]: moved InstallerError structure in essential.rs. Introduced InstallationError enum to group all the errors --- cli/src/essential.rs | 38 ++++++++++++++++++ cli/src/install.rs | 91 ++++++++++++++++++++++---------------------- 2 files changed, 84 insertions(+), 45 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 1c08a2f..7121e20 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use std::ptr::read; //TODO: Check if is possible to use the get_config_path function. Check for reusable components use std::{fs, io::stdin, path::PathBuf, process::exit}; +use std::fmt; use directories::ProjectDirs; use k8s_openapi::api::core::v1::ConfigMap; @@ -23,6 +24,43 @@ use kube::client::Client; pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command + +// docs: +// +// Custom error definition +// InstallerError: +// - used for general installation errors occured during the installation of cortexflow components. Can be used for: +// - Return downloading errors +// - Return unsuccessful file removal +// +// +// implements fmt::Display for user-friendly error messages +// + +#[derive(Debug, Clone)] +pub struct InstallerError { + pub(crate) reason: String, +} + +impl fmt::Display for InstallerError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "An error occured while installing cortexflow components. Reason: {}", + self.reason + ); + Ok(()) + } +} + +pub enum InstallationError{ + InstallerError{ + reason: String + }, + ClientError(kube::Error) +} + + //pub struct GeneralData { // env: String, //} diff --git a/cli/src/install.rs b/cli/src/install.rs index af58d26..98bff12 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -1,41 +1,20 @@ use colored::Colorize; -use kube::Client; +use kube::{ Client, core::ErrorResponse }; use tracing::debug; use clap::{ Args, Subcommand, command }; use std::{ process::{ Command, exit }, fmt, thread, time::Duration }; use crate::{ - essential::{ connect_to_client, create_config_file, create_configs, read_configs,BASE_COMMAND }, + essential::{ + connect_to_client, + create_config_file, + create_configs, + read_configs, + BASE_COMMAND, + InstallationError, + }, install, }; - - -// docs: -// -// Custom error definition -// InstallerError: -// - used for general installation errors occured during the installation of cortexflow components. Can be used for: -// - Return downloading errors -// - Return unsuccessful file removal -// -// -// implements fmt::Display for user-friendly error messages -// - -#[derive(Debug, Clone)] -struct InstallerError { - reason: String, -} - -impl fmt::Display for InstallerError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "An error occured while installing cortexflow components. Reason: {}", - self.reason - ); - Ok(()) - } -} +use kube::Error; // docs: // @@ -115,7 +94,7 @@ pub fn install_simple_example() { // // Returns an InstallerError if something fails -async fn install_cluster_components() -> Result<(), InstallerError> { +async fn install_cluster_components() -> Result<(), InstallationError> { match connect_to_client().await { Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); @@ -148,7 +127,16 @@ async fn install_cluster_components() -> Result<(), InstallerError> { Ok(()) } Err(e) => { - return Err(InstallerError { reason: "Can't connect to kubernetes client".to_string() }); + Err( + InstallationError::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, + }) + ) + ) } } } @@ -164,7 +152,7 @@ async fn install_cluster_components() -> Result<(), InstallerError> { // // Returns an InstallerError if something fails -async fn install_simple_example_component() -> Result<(), InstallerError> { +async fn install_simple_example_component() -> Result<(), InstallationError> { match connect_to_client().await { Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); @@ -183,7 +171,16 @@ async fn install_simple_example_component() -> Result<(), InstallerError> { Ok(()) } Err(e) => { - return Err(InstallerError { reason: "Can't connect to kubernetes client".to_string() }); + Err( + InstallationError::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, + }) + ) + ) } } } @@ -196,7 +193,7 @@ async fn install_simple_example_component() -> Result<(), InstallerError> { // - Executes the apply_component function // -fn install_components(components_type: &str) -> Result<(), InstallerError> { +fn install_components(components_type: &str) -> Result<(), InstallationError> { if components_type == "cortexbrain" { let files_to_install = vec![ "configmap-role.yaml", @@ -246,7 +243,7 @@ fn install_components(components_type: &str) -> Result<(), InstallerError> { i = i + 1; } } else { - return Err(InstallerError { + return Err(InstallationError::InstallerError { reason: "An error occured: No installation type selected".to_string(), }); } @@ -262,11 +259,13 @@ fn install_components(components_type: &str) -> Result<(), InstallerError> { // // Returns an InstallerError if something fails -fn apply_component(file: &str) -> Result<(), InstallerError> { +fn apply_component(file: &str) -> Result<(), InstallationError> { let output = Command::new(BASE_COMMAND) .args(["apply", "-f", file]) .output() - .map_err(|_| InstallerError { reason: "Can't install component from file".to_string() })?; + .map_err(|_| InstallationError::InstallerError { + reason: "Can't install component from file".to_string(), + })?; if !output.status.success() { eprintln!("Error installing file: {}:\n{}", file, String::from_utf8_lossy(&output.stderr)); @@ -286,7 +285,9 @@ fn apply_component(file: &str) -> Result<(), InstallerError> { // // Returns an InstallerError if something fails -fn download_installation_files(installation_files: InstallationType) -> Result<(), InstallerError> { +fn download_installation_files( + installation_files: InstallationType +) -> Result<(), InstallationError> { match installation_files { InstallationType::Components(files) => { for src in files.iter() { @@ -310,7 +311,7 @@ fn download_installation_files(installation_files: InstallationType) -> Result<( // // Returns an InstallerError if something fails -fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), InstallerError> { +fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), InstallationError> { println!("{} {}", "=====>".blue().bold(), "Removing temporary installation files".white()); match file_to_remove { InstallationType::Components(files) => { @@ -335,11 +336,11 @@ fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), Install // // Returns a InstallerError if something fails -fn download_file(src: &str) -> Result<(), InstallerError> { +fn download_file(src: &str) -> Result<(), InstallationError> { let output = Command::new("wget") .args([src]) .output() - .map_err(|_| InstallerError { + .map_err(|_| InstallationError::InstallerError { reason: "An error occured: component download failed".to_string(), })?; @@ -361,11 +362,11 @@ fn download_file(src: &str) -> Result<(), InstallerError> { // // Returns an InstallerError if something fails -fn rm_file(file_to_remove: &str) -> Result<(), InstallerError> { +fn rm_file(file_to_remove: &str) -> Result<(), InstallationError> { let output = Command::new("rm") .args(["-f", file_to_remove]) .output() - .map_err(|_| InstallerError { + .map_err(|_| InstallationError::InstallerError { reason: "cannot remove temporary installation file".to_string(), })?; From ae4f9de596a48c8b0b39b8aad8e2de6feb9e5b47 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 7 Nov 2025 09:38:48 +0100 Subject: [PATCH 07/13] [Code Refactoring]: removed duplicated functions. Better error handling. Added documentation to every function --- cli/src/service.rs | 297 ++++++++++++++++++++++----------------------- 1 file changed, 146 insertions(+), 151 deletions(-) diff --git a/cli/src/service.rs b/cli/src/service.rs index 42788a1..7510654 100644 --- a/cli/src/service.rs +++ b/cli/src/service.rs @@ -1,11 +1,10 @@ -use std::process::exit; -use std::str; -use std::{ io::Error, process::Command }; - -use crate::essential::{ Environments, get_config_directory, read_configs }; +use std::{ str, process::Command }; use colored::Colorize; - use clap::{ Args, Subcommand }; +use kube::{ core::ErrorResponse, Error }; + +use crate::essential::{ BASE_COMMAND, connect_to_client }; +use crate::logs::{ get_available_namespaces, check_namespace_exists }; //service subcommands #[derive(Subcommand, Debug, Clone)] @@ -26,77 +25,30 @@ pub struct ServiceArgs { pub service_cmd: ServiceCommands, } -fn check_namespace_exists(namespace: &str) -> bool { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env).args(["get", "namespace", namespace]).output(); - - match output { - Ok(output) => output.status.success(), - Err(_) => false, - } - } - Err(_) => false, - } -} - -fn get_available_namespaces() -> Vec { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) - .args([ - "get", - "namespaces", - "--no-headers", - "-o", - "custom-columns=NAME:.metadata.name", - ]) - .output(); - - match output { - Ok(output) if output.status.success() => { - let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout - .lines() - .map(|line| line.trim().to_string()) - .filter(|line| !line.is_empty()) - .collect() - } - _ => Vec::new(), - } - } - Err(_) => Vec::new(), - } -} - -pub fn list_services(namespace: Option) -> Result<(), Error> { - //TODO: maybe we can list both services and pods - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); +// docs: +// +// This is the main function that lists all the services in the cluster +// Steps: +// - connects to kubernetes client +// - check if the namespace exists +// - if the cortexflow namespace exists returns the service list +// - else return an empty Vector +// +// +// Returns a kube::Error if the connection fails + +pub async fn list_services(namespace: Option) -> Result<(), kube::Error> { + //TODO: maybe we can list both services and pods? + + match connect_to_client().await { + Ok(_) => { let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); println!("{} {} {}", "=====>".blue().bold(), "Listing services in namespace:", ns); // Check if namespace exists first - if !check_namespace_exists(&ns) { - let available_namespaces = get_available_namespaces(); + if !check_namespace_exists(&ns).await? { + let available_namespaces = get_available_namespaces().await?; println!("\nāŒ Namespace '{}' not found", ns); println!("{}", "=".repeat(50)); @@ -109,19 +61,18 @@ pub fn list_services(namespace: Option) -> Result<(), Error> { } else { println!("No namespaces found in the cluster."); } - - std::process::exit(1); } // kubectl command to get services - let output = Command::new(env).args(["get", "svc", "-n", &ns, "--no-headers"]).output(); + let output = Command::new(BASE_COMMAND) + .args(["get", "svc", "-n", &ns, "--no-headers"]) + .output(); match output { Ok(output) => { if !output.status.success() { let error = str::from_utf8(&output.stderr).unwrap_or("Unknown error"); - eprintln!("Error executing {}: {}", env, error); - std::process::exit(1); + eprintln!("Error executing {}: {}", BASE_COMMAND, error); } let stdout = str::from_utf8(&output.stdout).unwrap_or(""); @@ -133,7 +84,6 @@ pub fn list_services(namespace: Option) -> Result<(), Error> { "No services found in namespace", ns ); - exit(1); } // header for Table @@ -165,94 +115,139 @@ pub fn list_services(namespace: Option) -> Result<(), Error> { ); } } + Ok(()) } Err(err) => { - eprintln!("Failed to execute {} command: {}", env, err); - eprintln!("Make sure {} is installed and configured properly", env); - std::process::exit(1); + Err( + Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to execute kubectl describe command".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }) + ) } } } - Err(e) => { - eprintln!("Error reading the cluster environment from config files: {:?}", e); + Err(_) => { + Err( + Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to execute kubectl describe command".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }) + ) } } - Ok(()) } -pub fn describe_service(service_name: String, namespace: &Option) { - match list_services(namespace.clone()) { +// docs: +// +// This is the main function to describe a kubernetes service +// Steps: +// - connects to kubernetes client +// - check if the namespace exists +// - if the cortexflow namespace exists executes the kubectl describe command +// - output the result of the command +// - else return an empty Vector +// +// +// Returns a kube::Error if the connection fails + +pub async fn describe_service( + service_name: String, + namespace: &Option +) -> Result<(), kube::Error> { + match connect_to_client().await { Ok(_) => { - let file_path = get_config_directory().unwrap().1; - - let env = "kubectl".to_string(); - - let ns = namespace.clone().unwrap_or_else(|| "cortexflow".to_string()); - - println!( - "{} {} {} {} {}", - "=====>".blue().bold(), - "Describing service", - "in namespace:", - service_name, - ns - ); - println!("{}", "=".repeat(60)); - - // Check if namespace exists first - if !check_namespace_exists(&ns) { - let available_namespaces = get_available_namespaces(); - - println!("\nāŒ Namespace '{}' not found", ns); - println!("{}", "=".repeat(50)); - - if !available_namespaces.is_empty() { - println!("\nšŸ“‹ Available namespaces:"); - for available_ns in &available_namespaces { - println!(" • {}", available_ns); - } - println!("\nTry: cortex service describe {} --namespace ", service_name); - } else { - println!("No namespaces found in the cluster."); - } - - std::process::exit(1); - } - - // Execute kubectl describe pod command - let output = Command::new(env) - .args(["describe", "pod", &service_name, "-n", &ns]) - .output(); - - match output { - Ok(output) => { - if !output.status.success() { - let error = str::from_utf8(&output.stderr).unwrap_or("Unknown error"); - eprintln!("Error executing kubectl describe: {}", error); - eprintln!( - "Make sure the pod '{}' exists in namespace '{}'", - service_name, - ns - ); - std::process::exit(1); + match list_services(namespace.clone()).await { + Ok(__) => { + //let file_path = get_config_directory().unwrap().1; + + let ns = namespace.clone().unwrap_or_else(|| "cortexflow".to_string()); + + println!( + "{} {} {} {} {}", + "=====>".blue().bold(), + "Describing service", + "in namespace:", + service_name, + ns + ); + println!("{}", "=".repeat(60)); + + // Check if namespace exists first + if !check_namespace_exists(&ns).await? { + let available_namespaces = get_available_namespaces().await?; + + println!("\nāŒ Namespace '{}' not found", ns); + println!("{}", "=".repeat(50)); + + if !available_namespaces.is_empty() { + println!("\nšŸ“‹ Available namespaces:"); + for available_ns in &available_namespaces { + println!(" • {}", available_ns); + } + println!("\nTry: cortex service describe {} --namespace ", service_name); + } else { + println!("No namespaces found in the cluster."); + } } - let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - - if stdout.trim().is_empty() { - println!("No description found for pod '{}'", service_name); + // Execute kubectl describe pod command + let output = Command::new(BASE_COMMAND) + .args(["describe", "pod", &service_name, "-n", &ns]) + .output(); + + match output { + Ok(output) => { + if !output.status.success() { + let error = str + ::from_utf8(&output.stderr) + .unwrap_or("Unknown error"); + eprintln!("Error executing kubectl describe: {}", error); + eprintln!( + "Make sure the pod '{}' exists in namespace '{}'", + service_name, + ns + ); + } + + let stdout = str::from_utf8(&output.stdout).unwrap_or(""); + + if stdout.trim().is_empty() { + println!("No description found for pod '{}'", service_name); + } + + // Print the full kubectl describe output + println!("{}", stdout); + Ok(()) + } + Err(err) => { + Err( + Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to execute kubectl describe command".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }) + ) + } } - - // Print the full kubectl describe output - println!("{}", stdout); - } - Err(err) => { - eprintln!("Failed to execute kubectl describe command: {}", err); - eprintln!("Make sure kubectl is installed and configured properly"); - std::process::exit(1); } + Err(e) => todo!(), } } - Err(_) => todo!(), + Err(_) => { + Err( + Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to execute kubectl describe command".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }) + ) + } } } From 488bbb25467f04776bc9b885b22f1a57e8199214 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 7 Nov 2025 10:18:15 +0100 Subject: [PATCH 08/13] [code refactoring]: status module refactoring: added documentation, better error handling, removed duplicated functions --- cli/src/status.rs | 390 +++++++++++++++++++++++----------------------- 1 file changed, 198 insertions(+), 192 deletions(-) diff --git a/cli/src/status.rs b/cli/src/status.rs index 772a64a..e7998cc 100644 --- a/cli/src/status.rs +++ b/cli/src/status.rs @@ -1,11 +1,10 @@ use colored::Colorize; -use std::process::Command; -use std::str; - -use crate::essential::{Environments, get_config_directory, read_configs}; - +use std::{ process::Command, str }; use clap::Args; +use kube::{ Error, core::ErrorResponse }; +use crate::logs::{ get_available_namespaces, check_namespace_exists }; +use crate::essential::{ BASE_COMMAND, connect_to_client }; #[derive(Debug)] pub enum OutputFormat { @@ -32,230 +31,233 @@ pub struct StatusArgs { pub namespace: Option, } -pub fn status_command(output_format: Option, namespace: Option) { - let format = output_format - .map(OutputFormat::from) - .unwrap_or(OutputFormat::Text); - let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); - - println!( - "{} {} {}", - "=====>".blue().bold(), - "Checking CortexFlow status for namespace: ", - ns - ); - - // namespace checking - let namespace_status = check_namespace_exists(&ns); - - // If namespace doesn't exist, display error with available namespaces and exit - if !namespace_status { - let available_namespaces = get_available_namespaces(); - - match format { - OutputFormat::Text => { - println!("\nāŒ Namespace Status Check Failed"); - println!("{}", "=".repeat(50)); - println!(" āŒ {} namespace: NOT FOUND", ns); - - if !available_namespaces.is_empty() { - println!("\nšŸ“‹ Available namespaces:"); - for available_ns in &available_namespaces { - println!(" • {}", available_ns); +// docs: +// +// This is the main function for the status command. The status command display all the pods and services status in 3 types of format : Text, Json,Yaml +// defaul type is Text +// +// Steps: +// - connects to kubernetes client +// - check if the given namespace exists +// - if the namespace exists +// - return the pods status and the service status for all the pods and services in the namespace +// - else +// - return a failed state +// +// Returns a kube::Error if the connection fails + +pub async fn status_command( + output_format: Option, + namespace: Option +) -> Result<(), kube::Error> { + match connect_to_client().await { + Ok(_) => { + let format = output_format.map(OutputFormat::from).unwrap_or(OutputFormat::Text); + let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); + + println!( + "{} {} {}", + "=====>".blue().bold(), + "Checking CortexFlow status for namespace: ", + ns + ); + + // namespace checking + let namespace_status = check_namespace_exists(&ns).await?; + + // If namespace doesn't exist, display error with available namespaces and exit + if !namespace_status { + let available_namespaces = get_available_namespaces().await?; + + match format { + OutputFormat::Text => { + println!("\nāŒ Namespace Status Check Failed"); + println!("{}", "=".repeat(50)); + println!(" āŒ {} namespace: NOT FOUND", ns); + + if !available_namespaces.is_empty() { + println!("\nšŸ“‹ Available namespaces:"); + for available_ns in &available_namespaces { + println!(" • {}", available_ns); + } + } + } + OutputFormat::Json => { + println!("{{"); + println!(" \"error\": \"{} namespace not found\",", ns); + println!(" \"namespace\": {{"); + println!(" \"name\": \"{}\",", ns); + println!(" \"exists\": false"); + println!(" }},"); + println!(" \"available_namespaces\": ["); + for (i, ns) in available_namespaces.iter().enumerate() { + let comma = if i == available_namespaces.len() - 1 { "" } else { "," }; + println!(" \"{}\"{}", ns, comma); + } + println!(" ]"); + println!("}}"); + } + OutputFormat::Yaml => { + println!("error: {} namespace not found", ns); + println!("namespace:"); + println!(" name: {}", ns); + println!(" exists: false"); + println!("available_namespaces:"); + for ns in &available_namespaces { + println!(" - {}", ns); + } } } } - OutputFormat::Json => { - println!("{{"); - println!(" \"error\": \"{} namespace not found\",", ns); - println!(" \"namespace\": {{"); - println!(" \"name\": \"{}\",", ns); - println!(" \"exists\": false"); - println!(" }},"); - println!(" \"available_namespaces\": ["); - for (i, ns) in available_namespaces.iter().enumerate() { - let comma = if i == available_namespaces.len() - 1 { - "" - } else { - "," - }; - println!(" \"{}\"{}", ns, comma); - } - println!(" ]"); - println!("}}"); - } - OutputFormat::Yaml => { - println!("error: {} namespace not found", ns); - println!("namespace:"); - println!(" name: {}", ns); - println!(" exists: false"); - println!("available_namespaces:"); - for ns in &available_namespaces { - println!(" - {}", ns); - } - } - } - std::process::exit(1); - } - - // get pods and services only if namespace exists - let pods_status = get_pods_status(&ns); - let services_status = get_services_status(&ns); - - // display options (format) - match format { - OutputFormat::Text => { - display_text_format(&ns, namespace_status, pods_status, services_status) - } - OutputFormat::Json => { - display_json_format(&ns, namespace_status, pods_status, services_status) - } - OutputFormat::Yaml => { - display_yaml_format(&ns, namespace_status, pods_status, services_status) - } - } -} - -fn check_namespace_exists(namespace: &str) -> bool { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) - .args(["get", "namespace", namespace]) - .output(); - - match output { - Ok(output) => output.status.success(), - Err(_) => false, - } - } - Err(_) => false, - } -} -fn get_available_namespaces() -> Vec { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) - .args([ - "get", - "namespaces", - "--no-headers", - "-o", - "custom-columns=NAME:.metadata.name", - ]) - .output(); + // get pods and services only if namespace exists + let pods_status = get_pods_status(&ns).await?; + let services_status = get_services_status(&ns).await?; - match output { - Ok(output) if output.status.success() => { - let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout - .lines() - .map(|line| line.trim().to_string()) - .filter(|line| !line.is_empty()) - .collect() + // display options (format) + match format { + OutputFormat::Text => { + display_text_format(&ns, namespace_status, pods_status, services_status); + Ok(()) + } + OutputFormat::Json => { + display_json_format(&ns, namespace_status, pods_status, services_status); + Ok(()) + } + OutputFormat::Yaml => { + display_yaml_format(&ns, namespace_status, pods_status, services_status); + Ok(()) } - _ => Vec::new(), } } - Err(_) => Vec::new(), + Err(_) => { + Err( + 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, + }) + ) + } } } -fn get_pods_status(namespace: &str) -> Vec<(String, String, String)> { - let file_path = get_config_directory().unwrap().1; - - let env_from_file = "kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) +// docs: +// +// This is an auxiliary function that returns the status for a given pod +// Steps: +// - connects to kubernetes client +// - return the pod status in this format: (name,ready?,status) +// +// Returns a kube::Error if the connection fails + +async fn get_pods_status(namespace: &str) -> Result, kube::Error> { + match connect_to_client().await { + Ok(_) => { + let output = Command::new(BASE_COMMAND) .args(["get", "pods", "-n", namespace, "--no-headers"]) .output(); match output { Ok(output) if output.status.success() => { let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout - .lines() - .filter_map(|line| { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 3 { - Some(( - parts[0].to_string(), // name - parts[1].to_string(), // ready - parts[2].to_string(), // status - )) - } else { - None - } - }) - .collect() + Ok( + stdout + .lines() + .filter_map(|line| { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 3 { + Some(( + parts[0].to_string(), // name + parts[1].to_string(), // ready + parts[2].to_string(), // status + )) + } else { + None + } + }) + .collect() + ) } - _ => Vec::new(), + _ => Ok(Vec::new()), } } - Err(_) => Vec::new(), + Err(_) => { + Err( + 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, + }) + ) + } } } -fn get_services_status(namespace: &str) -> Vec<(String, String, String)> { - let file_path = get_config_directory().unwrap().1; - - let env_from_file ="kubernetes".to_string(); - let user_env = Environments::try_from(env_from_file.to_lowercase()); - - match user_env { - Ok(cluster_environment) => { - let env = cluster_environment.base_command(); - let output = Command::new(env) +// docs: +// +// This is an auxiliary function that returns the status for a given service +// Steps: +// - connects to kubernetes client +// - return the service status in this format: (name,type,cluster ips) +// +// Returns a kube::Error if the connection fails + +async fn get_services_status( + namespace: &str +) -> Result, kube::Error> { + match connect_to_client().await { + Ok(_) => { + let output = Command::new(BASE_COMMAND) .args(["get", "services", "-n", namespace, "--no-headers"]) .output(); match output { Ok(output) if output.status.success() => { let stdout = str::from_utf8(&output.stdout).unwrap_or(""); - stdout - .lines() - .filter_map(|line| { - let parts: Vec<&str> = line.split_whitespace().collect(); - if parts.len() >= 4 { - Some(( - parts[0].to_string(), // name - parts[1].to_string(), // type - parts[2].to_string(), // cluster ips - )) - } else { - None - } - }) - .collect() + Ok( + stdout + .lines() + .filter_map(|line| { + let parts: Vec<&str> = line.split_whitespace().collect(); + if parts.len() >= 4 { + Some(( + parts[0].to_string(), // name + parts[1].to_string(), // type + parts[2].to_string(), // cluster ips + )) + } else { + None + } + }) + .collect() + ) } - _ => Vec::new(), + _ => Ok(Vec::new()), } } - Err(_) => Vec::new(), + Err(_) => { + Err( + 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, + }) + ) + } } } +// docs: displays outputs in a text format + fn display_text_format( ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, - services: Vec<(String, String, String)>, + services: Vec<(String, String, String)> ) { println!("\nšŸ” CortexFlow Status Report"); println!("{}", "=".repeat(50)); @@ -289,11 +291,13 @@ fn display_text_format( println!("\n{}", "=".repeat(50)); } +// docs: displays outputs in a json format + fn display_json_format( ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, - services: Vec<(String, String, String)>, + services: Vec<(String, String, String)> ) { println!("{{"); println!(" \"namespace\": {{"); @@ -325,11 +329,13 @@ fn display_json_format( println!("}}"); } +// docs: displays outputs in a yaml format + fn display_yaml_format( ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, - services: Vec<(String, String, String)>, + services: Vec<(String, String, String)> ) { println!("namespace:"); println!(" name: {}", ns); From 61e7e36ac214c67fc688b5f2edeadb36f5eb31bb Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 7 Nov 2025 11:44:42 +0100 Subject: [PATCH 09/13] [Code refactoring]: refactored essential.rs module: added documentation to every function, better error handling --- cli/src/essential.rs | 393 +++++++++++++++++++++++++------------------ 1 file changed, 226 insertions(+), 167 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 7121e20..ef27016 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -1,30 +1,17 @@ -use std::collections::BTreeMap; -use std::ptr::read; -//TODO: Check if is possible to use the get_config_path function. Check for reusable components -use std::{fs, io::stdin, path::PathBuf, process::exit}; -use std::fmt; +use std::{ collections::BTreeMap, fmt, process::Command, result::Result::Ok }; -use directories::ProjectDirs; -use k8s_openapi::api::core::v1::ConfigMap; -use k8s_openapi::serde_json::json; -use kube::Config; -use prost_types::MethodDescriptorProto; +use kube::core::ErrorResponse; use serde::Serialize; -use std::fs::{Metadata, OpenOptions}; -use std::result::Result::Ok; - use colored::Colorize; -use std::thread; -use std::time::Duration; - -use std::process::Command; -use kube::api::{Api, ObjectMeta, Patch, PatchParams, PostParams}; +use k8s_openapi::api::core::v1::ConfigMap; +use k8s_openapi::serde_json::json; +use kube::{ Config, Error }; +use kube::api::{ Api, ObjectMeta, Patch, PatchParams, PostParams }; use kube::client::Client; pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command - // docs: // // Custom error definition @@ -53,84 +40,51 @@ impl fmt::Display for InstallerError { } } -pub enum InstallationError{ - InstallerError{ - reason: String +// docs: +// +// Custom enum definition to group all the installation error for cortexflow +// + +pub enum InstallationError { + InstallerError { + reason: String, }, - ClientError(kube::Error) + ClientError(kube::Error), } - -//pub struct GeneralData { -// env: String, -//} #[derive(Serialize)] pub struct MetadataConfigFile { blocklist: Vec, } -//FIXME: remove this part -//#[derive(Debug)] -//pub enum Environments { -// Kubernetes, -//} -//impl TryFrom<&str> for Environments { -// type Error = String; +// docs: +// +// This is a wrapper functions used to create a kubernetes client session +// Used in modules: +// - install +// - logs +// - service +// - status +// - uninstall // -// fn try_from(environment: &str) -> Result { -// match environment { -// "kubernetes" | "k8s" => Ok(Environments::Kubernetes), -// _ => -// Err( -// format!("Environment '{}' not supported. Please insert a supported value: Kubernetes, K8s", environment) -// ), -// } -// } -//} - -//for owned types -//impl TryFrom for Environments { -// type Error = String; // -// fn try_from(environment: String) -> Result { -// Environments::try_from(environment.as_str()) -// } -//} - -//impl Environments { -// pub fn base_command(&self) -> &'static str { -// match self { -// Environments::Kubernetes => "kubectl", -// } -// } -//} - -//impl GeneralData { - //pub const VERSION: &str = env!("CARGO_PKG_VERSION"); - //pub const AUTHOR: &str = env!("CARGO_PKG_AUTHORS"); - //pub const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION"); - - //pub fn new(env: String) -> Self { - // GeneralData { - // env: env.to_string(), // FIXME: remove this field - // } - //} - //pub fn set_env(mut self, env: String) { - // self.env = env; - //} - //pub fn get_env(self) -> String { - // self.env - //} - //pub fn get_env_output(self) { - // println!("{:?}", self.env) - //} -//} +// Returns a Result with the client an a kube::Error pub async fn connect_to_client() -> Result { let client = Client::try_default().await; client } +// docs: +// +// This is an function used to update the cli +// +// Steps: +// - Checks the current CLI version +// - if the version matches the current latest version doesn't do anything +// - else runs the cargo update command +// +// Returns an error if the command fails pub fn update_cli() { println!("{} {}", "=====>".blue().bold(), "Updating CortexFlow CLI"); @@ -144,12 +98,20 @@ pub fn update_cli() { println!("āœ… Updated CLI"); } } -//pub fn info(general_data: GeneralData) { -// println!("{} {} {}", "=====>".blue().bold(), "Version:", GeneralData::VERSION); -// println!("{} {} {}", "=====>".blue().bold(), "Author:", GeneralData::AUTHOR); -// println!("{} {} {}", "=====>".blue().bold(), "Description:", GeneralData::DESCRIPTION); -// println!("{} {} {}", "=====>".blue().bold(), "Environment:", general_data.get_env()); // FIXME: remove this field -//} + +// docs: +// +// 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")); +} + +// docs: +// +// This is a wrapper function to create the MetadataConfigFile structure pub fn create_configs() -> MetadataConfigFile { let mut blocklist: Vec = Vec::new(); @@ -158,63 +120,134 @@ pub fn create_configs() -> MetadataConfigFile { let configs = MetadataConfigFile { blocklist }; configs } -pub async fn read_configs() -> Result, anyhow::Error> { - let client = Client::try_default().await?; - let namespace = "cortexflow"; - let configmap = "cortexbrain-client-config"; - let api: Api = Api::namespaced(client, namespace); - - let cm = api.get(configmap).await?; - - if let Some(data) = cm.data { - if let Some(blocklist_raw) = data.get("blocklist") { - let lines: Vec = blocklist_raw - .lines() - .map(|s| s.trim().to_string()) - .filter(|s| !s.is_empty()) // ignora righe vuote - .collect(); - - return Ok(lines); + +// docs: +// +// This is an helper functions used to read the configs from a kubernetes configmap +// +// Steps: +// - connects to kubernetes client +// - read the configmap from the kubernetes API. Needed: namespace_name , configmap_name +// - returns the given configmap blocklist data in a Vec type +// +// Returns an error if something fails + +pub async fn read_configs() -> Result, kube::Error> { + match connect_to_client().await { + Ok(client) => { + let namespace = "cortexflow"; + let configmap = "cortexbrain-client-config"; + let api: Api = Api::namespaced(client, namespace); + + let cm = api.get(configmap).await?; + + if let Some(data) = cm.data { + if let Some(blocklist_raw) = data.get("blocklist") { + let lines: Vec = blocklist_raw + .lines() + .map(|s| s.trim().to_string()) + .filter(|s| !s.is_empty()) // ignore empty lines + .collect(); + + return Ok(lines); + } + } + + Ok(Vec::new()) //in case the key fails + } + Err(_) => { + Err( + 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, + }) + ) } } - - Ok(Vec::new()) //in case the key fails } -pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), anyhow::Error> { - let client = Client::try_default().await?; - let namespace = "cortexflow"; - let configmap = "cortexbrain-client-config"; - let api: Api = Api::namespaced(client, namespace); - - // create configmap - let mut data = BTreeMap::new(); - for x in config_struct.blocklist { - data.insert("blocklist".to_string(), x); - } - let cm = ConfigMap { - metadata: ObjectMeta { - name: Some("cortexbrain-client-config".to_string()), - ..Default::default() - }, // type ObjectMeta - data: Some(data), //type Option> - ..Default::default() - }; - match api.create(&PostParams::default(), &cm).await { - Ok(_) => { - println!("Configmap created successfully"); +// docs: +// +// This is a function used to create a configmap file +// +// With the version 0.1.4 cortexflow introduced a configmap file to store the relevant cortexflow metadata +// Up to now the metadata includes: +// - blocked ip addresses passed using the CLI +// +// Steps: +// - connects to kubernetes client +// - creates a configmap named "cortexbrain-client-config" stored in the cortexflow namespace +// - the blocklist field is initialized with zero blocked addresses +// +// Returns an error if something fails + +pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), kube::Error> { + match connect_to_client().await { + Ok(client) => { + let namespace = "cortexflow"; + let configmap = "cortexbrain-client-config"; + + let api: Api = Api::namespaced(client, namespace); + + // create configmap + let mut data = BTreeMap::new(); + for x in config_struct.blocklist { + data.insert("blocklist".to_string(), x); + } + let cm = ConfigMap { + metadata: ObjectMeta { + name: Some("cortexbrain-client-config".to_string()), + ..Default::default() + }, // type ObjectMeta + data: Some(data), //type Option> + ..Default::default() + }; + match api.create(&PostParams::default(), &cm).await { + Ok(_) => { + println!("Configmap created successfully"); + } + Err(e) => { + eprintln!("An error occured: {}", e); + } + } + Ok(()) } - Err(e) => { - eprintln!("An error occured: {}", e); + Err(_) => { + Err( + 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, + }) + ) } - }; - Ok(()) + } } -pub async fn update_config_metadata(input: &str, action: &str) { +// docs: +// +// This is a function used to update a configmap file. Takes an input and an action +// +// Input: an ip (&str type) +// Actions: +// - Add: add the ip to the blocklist metadata +// - Delete: remove the ip from the blocklist metadata +// +// Steps: +// - connects to kubernetes client +// - reads the existing configmap +// - creates a temporary vector with the old addresses and the new address +// - creates a patch by calling the update_configamp file +// +// Returns an error if something fails + +pub async fn update_config_metadata(input: &str, action: &str) -> Result<(), kube::Error> { if action == "add" { //retrieve current blocked ips list - let mut ips = read_configs().await.unwrap(); + let mut ips = read_configs().await?; println!("Readed current blocked ips: {:?}", ips); //create a temporary vector of ips @@ -223,9 +256,9 @@ pub async fn update_config_metadata(input: &str, action: &str) { // override blocklist parameters let new_configs = MetadataConfigFile { blocklist: ips }; //create a new config - update_configmap(new_configs).await; + update_configmap(new_configs).await?; } else if action == "delete" { - let mut ips = read_configs().await.unwrap(); + let mut ips = read_configs().await?; if let Some(index) = ips.iter().position(|target| target == &input.to_string()) { ips.remove(index); } else { @@ -235,41 +268,67 @@ pub async fn update_config_metadata(input: &str, action: &str) { // override blocklist parameters let new_configs = MetadataConfigFile { blocklist: ips }; //create a new config - update_configmap(new_configs).await; + update_configmap(new_configs).await?; } + Ok(()) } -pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), anyhow::Error> { - let client = Client::try_default().await?; - let namespace = "cortexflow"; - let name = "cortexbrain-client-config"; - let api: Api = Api::namespaced(client, namespace); - - 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_params = PatchParams::apply("cortexbrain").force(); - match api.patch(name, &patch_params, &patch).await { - Ok(_) => { - println!("Map updated successfully"); +// docs: +// +// This is a function used to create a patch to update a configmap +// +// Steps: +// - connects to kubernetes client +// - creates a patch using the config_struct data +// - pushes the patch to the kubernetes API +// +// Returns an error if something fails + +pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), kube::Error> { + match connect_to_client().await { + Ok(client) => { + let namespace = "cortexflow"; + let name = "cortexbrain-client-config"; + let api: Api = Api::namespaced(client, namespace); + + 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_params = PatchParams::apply("cortexbrain").force(); + match api.patch(name, &patch_params, &patch).await { + Ok(_) => { + println!("Map updated successfully"); + } + Err(e) => { + eprintln!("An error occured during the patching process: {}", e); + return Err(e.into()); + } + } + + Ok(()) } - Err(e) => { - eprintln!("An error occured during the patching process: {}", e); - return Err(e.into()); + Err(_) => { + Err( + 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, + }) + ) } } - - Ok(()) } From 00fe6d03b56de2bd46d6cc165e1489843176b0b4 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 7 Nov 2025 13:57:59 +0100 Subject: [PATCH 10/13] [code refactoring]: Wrapped all different error type in a CliError enum to group all the error types. implemented From trait for ClientError to convert kube::Error erros in ClientError's type --- cli/src/essential.rs | 83 ++++++++++++++++----------- cli/src/install.rs | 46 +++++++-------- cli/src/logs.rs | 130 +++++++++++++++++++++++-------------------- cli/src/service.rs | 68 ++++++++++++---------- cli/src/status.rs | 58 ++++++++++--------- cli/src/uninstall.rs | 26 ++++----- 6 files changed, 225 insertions(+), 186 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index ef27016..4c89c82 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -12,6 +12,26 @@ use kube::client::Client; pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command +// docs: +// +// Custom enum definition to group all the installation error for cortexflow +// + +pub enum CliError { + InstallerError { + reason: String, + }, + ClientError(kube::Error), + UninstallError { + reason: String, + }, +} +impl From for CliError { + fn from(e: Error) -> Self { + CliError::ClientError(e) + } +} + // docs: // // Custom error definition @@ -40,18 +60,6 @@ impl fmt::Display for InstallerError { } } -// docs: -// -// Custom enum definition to group all the installation error for cortexflow -// - -pub enum InstallationError { - InstallerError { - reason: String, - }, - ClientError(kube::Error), -} - #[derive(Serialize)] pub struct MetadataConfigFile { blocklist: Vec, @@ -61,6 +69,7 @@ pub struct MetadataConfigFile { // // This is a wrapper functions used to create a kubernetes client session // Used in modules: +// - essentials // - install // - logs // - service @@ -132,7 +141,7 @@ pub fn create_configs() -> MetadataConfigFile { // // Returns an error if something fails -pub async fn read_configs() -> Result, kube::Error> { +pub async fn read_configs() -> Result, CliError> { match connect_to_client().await { Ok(client) => { let namespace = "cortexflow"; @@ -157,12 +166,14 @@ pub async fn read_configs() -> Result, kube::Error> { } Err(_) => { Err( - 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, - }) + 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, + }) + ) ) } } @@ -183,7 +194,7 @@ pub async fn read_configs() -> Result, kube::Error> { // // Returns an error if something fails -pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), kube::Error> { +pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), CliError> { match connect_to_client().await { Ok(client) => { let namespace = "cortexflow"; @@ -216,12 +227,14 @@ pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), } Err(_) => { Err( - 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, - }) + 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, + }) + ) ) } } @@ -244,7 +257,7 @@ pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), // // Returns an error if something fails -pub async fn update_config_metadata(input: &str, action: &str) -> Result<(), kube::Error> { +pub async fn update_config_metadata(input: &str, action: &str) -> Result<(), CliError> { if action == "add" { //retrieve current blocked ips list let mut ips = read_configs().await?; @@ -284,7 +297,7 @@ pub async fn update_config_metadata(input: &str, action: &str) -> Result<(), kub // // Returns an error if something fails -pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), kube::Error> { +pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), CliError> { match connect_to_client().await { Ok(client) => { let namespace = "cortexflow"; @@ -322,12 +335,14 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), k } Err(_) => { Err( - 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, - }) + 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, + }) + ) ) } } diff --git a/cli/src/install.rs b/cli/src/install.rs index 98bff12..bb97f5b 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -10,7 +10,7 @@ use crate::{ create_configs, read_configs, BASE_COMMAND, - InstallationError, + CliError, }, install, }; @@ -92,9 +92,9 @@ pub fn install_simple_example() { // - Executes the install_components function // - Executes the rm_installation_files to remove the temporary installation files // -// Returns an InstallerError if something fails +// Returns an CliError if something fails -async fn install_cluster_components() -> Result<(), InstallationError> { +async fn install_cluster_components() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); @@ -128,7 +128,7 @@ async fn install_cluster_components() -> Result<(), InstallationError> { } Err(e) => { Err( - InstallationError::ClientError( + CliError::ClientError( Error::Api(ErrorResponse { status: "failed".to_string(), message: "Failed to connect to kubernetes client".to_string(), @@ -150,9 +150,9 @@ async fn install_cluster_components() -> Result<(), InstallationError> { // - Executes the install_example function // - Executes the rm_example_installation_file to remove the temporary installation files // -// Returns an InstallerError if something fails +// Returns an CliError if something fails -async fn install_simple_example_component() -> Result<(), InstallationError> { +async fn install_simple_example_component() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Copying installation files".white()); @@ -172,7 +172,7 @@ async fn install_simple_example_component() -> Result<(), InstallationError> { } Err(e) => { Err( - InstallationError::ClientError( + CliError::ClientError( Error::Api(ErrorResponse { status: "failed".to_string(), message: "Failed to connect to kubernetes client".to_string(), @@ -193,7 +193,7 @@ async fn install_simple_example_component() -> Result<(), InstallationError> { // - Executes the apply_component function // -fn install_components(components_type: &str) -> Result<(), InstallationError> { +fn install_components(components_type: &str) -> Result<(), CliError> { if components_type == "cortexbrain" { let files_to_install = vec![ "configmap-role.yaml", @@ -243,8 +243,8 @@ fn install_components(components_type: &str) -> Result<(), InstallationError> { i = i + 1; } } else { - return Err(InstallationError::InstallerError { - reason: "An error occured: No installation type selected".to_string(), + return Err(CliError::InstallerError { + reason: "No installation type selected".to_string(), }); } Ok(()) @@ -257,13 +257,13 @@ fn install_components(components_type: &str) -> Result<(), InstallationError> { // - Read the file name of a kubernetes manifest (e.g agent.yaml) // - Applies the manifest using the command kubectl apply -f // -// Returns an InstallerError if something fails +// Returns an CliError if something fails -fn apply_component(file: &str) -> Result<(), InstallationError> { +fn apply_component(file: &str) -> Result<(), CliError> { let output = Command::new(BASE_COMMAND) .args(["apply", "-f", file]) .output() - .map_err(|_| InstallationError::InstallerError { + .map_err(|_| CliError::InstallerError { reason: "Can't install component from file".to_string(), })?; @@ -283,11 +283,11 @@ fn apply_component(file: &str) -> Result<(), InstallationError> { // - Read the Vec containing the file names of the installation files from the InstallationType enum // - Download the corresponding installation files from the github repository // -// Returns an InstallerError if something fails +// Returns an CliError if something fails fn download_installation_files( installation_files: InstallationType -) -> Result<(), InstallationError> { +) -> Result<(), CliError> { match installation_files { InstallationType::Components(files) => { for src in files.iter() { @@ -309,9 +309,9 @@ fn download_installation_files( // - Read the Vec containing the file names of the installation files from the InstallationType enum // - Executes the rm_file function for each installation file // -// Returns an InstallerError if something fails +// Returns an CliError if something fails -fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), InstallationError> { +fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), CliError> { println!("{} {}", "=====>".blue().bold(), "Removing temporary installation files".white()); match file_to_remove { InstallationType::Components(files) => { @@ -334,13 +334,13 @@ fn rm_installation_files(file_to_remove: InstallationType) -> Result<(), Install // - Read the url name of a kubernetes manifest // - Download the manifest file from the cortexflow repository // -// Returns a InstallerError if something fails +// Returns a CliError if something fails -fn download_file(src: &str) -> Result<(), InstallationError> { +fn download_file(src: &str) -> Result<(), CliError> { let output = Command::new("wget") .args([src]) .output() - .map_err(|_| InstallationError::InstallerError { + .map_err(|_| CliError::InstallerError { reason: "An error occured: component download failed".to_string(), })?; @@ -360,13 +360,13 @@ fn download_file(src: &str) -> Result<(), InstallationError> { // - Read the file name // - Removes the file using the rm -f // -// Returns an InstallerError if something fails +// Returns an CliError if something fails -fn rm_file(file_to_remove: &str) -> Result<(), InstallationError> { +fn rm_file(file_to_remove: &str) -> Result<(), CliError> { let output = Command::new("rm") .args(["-f", file_to_remove]) .output() - .map_err(|_| InstallationError::InstallerError { + .map_err(|_| CliError::InstallerError { reason: "cannot remove temporary installation file".to_string(), })?; diff --git a/cli/src/logs.rs b/cli/src/logs.rs index c0abc4e..bd819cc 100644 --- a/cli/src/logs.rs +++ b/cli/src/logs.rs @@ -2,7 +2,7 @@ use std::{ str, process::Command, result::Result::Ok }; use colored::Colorize; use clap::Args; use kube::{ Error, core::ErrorResponse }; -use crate::essential::{ connect_to_client, BASE_COMMAND }; +use crate::essential::{ connect_to_client, BASE_COMMAND, CliError }; #[derive(Args, Debug, Clone)] pub struct LogsArgs { @@ -48,13 +48,13 @@ impl Component { // - returns the list of namespaces in Vec format // // -// Returns a kube::Error if the connectiion to the kubeapi fails +// Returns a CliError if the connectiion to the kubeapi fails pub async fn logs_command( service: Option, component: Option, namespace: Option -) -> Result<(), kube::Error> { +) -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); @@ -148,12 +148,14 @@ pub async fn logs_command( } Err(_) => { Err( - 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, - }) + 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, + }) + ) ) } } @@ -167,9 +169,9 @@ pub async fn logs_command( // - returns true if the namespace exists or false if the namespace doesn't exists // // -// Returns a kube::Error if the connection fails +// Returns a CliError if the connection fails -async fn check_namespace_exists(namespace: &str) -> Result { +pub async fn check_namespace_exists(namespace: &str) -> Result { match connect_to_client().await { Ok(_) => { let output = Command::new(BASE_COMMAND).args(["get", "namespace", namespace]).output(); @@ -180,14 +182,16 @@ async fn check_namespace_exists(namespace: &str) -> Result { } } Err(_) => { - return Err( - 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( + 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, + }) + ) + ) } } } @@ -200,9 +204,9 @@ async fn check_namespace_exists(namespace: &str) -> Result { // - returns the list of namespaces in Vec format // // -// Returns a kube::Error if the connectiion to the kubeapi fails +// Returns a CliError if the connectiion to the kubeapi fails -async fn get_available_namespaces() -> Result, kube::Error> { +pub async fn get_available_namespaces() -> Result, CliError> { match connect_to_client().await { Ok(_) => { let output = Command::new(BASE_COMMAND) @@ -229,14 +233,16 @@ async fn get_available_namespaces() -> Result, kube::Error> { } } Err(_) => { - return Err( - 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( + 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, + }) + ) + ) } } } @@ -249,12 +255,12 @@ async fn get_available_namespaces() -> Result, kube::Error> { // - returns the list of pods associated with a kubernetes service filtering by labels in Vec format // // -// Returns a kube::Error if the connectiion to the kubeapi fails +// Returns a CliError if the connectiion to the kubeapi fails async fn get_pods_for_service( namespace: &str, service_name: &str -) -> Result, kube::Error> { +) -> Result, CliError> { match connect_to_client().await { Ok(_) => { let output = Command::new(BASE_COMMAND) @@ -285,14 +291,16 @@ async fn get_pods_for_service( } } Err(_) => { - return Err( - 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( + 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, + }) + ) + ) } } } @@ -306,12 +314,12 @@ async fn get_pods_for_service( // label selector,in Vec format // // -// Returns a kube::Error if the connectiion to the kubeapi fails +// Returns a CliError if the connectiion to the kubeapi fails async fn get_pods_for_component( namespace: &str, component: &Component -) -> Result, kube::Error> { +) -> Result, CliError> { match connect_to_client().await { Ok(_) => { let output = Command::new(BASE_COMMAND) @@ -342,14 +350,16 @@ async fn get_pods_for_component( } } Err(_) => { - return Err( - 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( + 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, + }) + ) + ) } } } @@ -362,9 +372,9 @@ async fn get_pods_for_component( // - returns the list of all pods in Vec format // // -// Returns a kube::Error if the connectiion to the kubeapi fails +// Returns a CliError if the connectiion to the kubeapi fails -async fn get_all_pods(namespace: &str) -> Result, kube::Error> { +async fn get_all_pods(namespace: &str) -> Result, CliError> { match connect_to_client().await { Ok(_) => { let output = Command::new(BASE_COMMAND) @@ -393,14 +403,16 @@ async fn get_all_pods(namespace: &str) -> Result, kube::Error> { } } Err(_) => { - return Err( - 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( + 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, + }) + ) + ) } } } diff --git a/cli/src/service.rs b/cli/src/service.rs index 7510654..b66ed7e 100644 --- a/cli/src/service.rs +++ b/cli/src/service.rs @@ -3,7 +3,7 @@ use colored::Colorize; use clap::{ Args, Subcommand }; use kube::{ core::ErrorResponse, Error }; -use crate::essential::{ BASE_COMMAND, connect_to_client }; +use crate::essential::{ BASE_COMMAND, connect_to_client, CliError }; use crate::logs::{ get_available_namespaces, check_namespace_exists }; //service subcommands @@ -35,9 +35,9 @@ pub struct ServiceArgs { // - else return an empty Vector // // -// Returns a kube::Error if the connection fails +// Returns a CliError if the connection fails -pub async fn list_services(namespace: Option) -> Result<(), kube::Error> { +pub async fn list_services(namespace: Option) -> Result<(), CliError> { //TODO: maybe we can list both services and pods? match connect_to_client().await { @@ -119,24 +119,28 @@ pub async fn list_services(namespace: Option) -> Result<(), kube::Error> } Err(err) => { Err( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to execute kubectl describe command".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) + CliError::ClientError( + Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to execute the kubectl command".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }) + ) ) } } } Err(_) => { Err( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to execute kubectl describe command".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) + 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, + }) + ) ) } } @@ -153,16 +157,16 @@ pub async fn list_services(namespace: Option) -> Result<(), kube::Error> // - else return an empty Vector // // -// Returns a kube::Error if the connection fails +// Returns a CliError if the connection fails pub async fn describe_service( service_name: String, namespace: &Option -) -> Result<(), kube::Error> { +) -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { match list_services(namespace.clone()).await { - Ok(__) => { + Ok(_) => { //let file_path = get_config_directory().unwrap().1; let ns = namespace.clone().unwrap_or_else(|| "cortexflow".to_string()); @@ -226,12 +230,14 @@ pub async fn describe_service( } Err(err) => { Err( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to execute kubectl describe command".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) + CliError::ClientError( + Error::Api(ErrorResponse { + status: "failed".to_string(), + message: "Failed to execute the kubectl command ".to_string(), + reason: "Your cluster is probably disconnected".to_string(), + code: 404, + }) + ) ) } } @@ -241,12 +247,14 @@ pub async fn describe_service( } Err(_) => { Err( - Error::Api(ErrorResponse { - status: "failed".to_string(), - message: "Failed to execute kubectl describe command".to_string(), - reason: "Your cluster is probably disconnected".to_string(), - code: 404, - }) + 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, + }) + ) ) } } diff --git a/cli/src/status.rs b/cli/src/status.rs index e7998cc..2680781 100644 --- a/cli/src/status.rs +++ b/cli/src/status.rs @@ -4,7 +4,7 @@ use clap::Args; use kube::{ Error, core::ErrorResponse }; use crate::logs::{ get_available_namespaces, check_namespace_exists }; -use crate::essential::{ BASE_COMMAND, connect_to_client }; +use crate::essential::{ BASE_COMMAND, connect_to_client, CliError }; #[derive(Debug)] pub enum OutputFormat { @@ -44,12 +44,12 @@ pub struct StatusArgs { // - else // - return a failed state // -// Returns a kube::Error if the connection fails +// Returns a CliError if the connection fails pub async fn status_command( output_format: Option, namespace: Option -) -> Result<(), kube::Error> { +) -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { let format = output_format.map(OutputFormat::from).unwrap_or(OutputFormat::Text); @@ -132,12 +132,14 @@ pub async fn status_command( } Err(_) => { Err( - 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, - }) + 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, + }) + ) ) } } @@ -150,9 +152,9 @@ pub async fn status_command( // - connects to kubernetes client // - return the pod status in this format: (name,ready?,status) // -// Returns a kube::Error if the connection fails +// Returns a CliError if the connection fails -async fn get_pods_status(namespace: &str) -> Result, kube::Error> { +async fn get_pods_status(namespace: &str) -> Result, CliError> { match connect_to_client().await { Ok(_) => { let output = Command::new(BASE_COMMAND) @@ -185,12 +187,14 @@ async fn get_pods_status(namespace: &str) -> Result { Err( - 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, - }) + 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, + }) + ) ) } } @@ -203,11 +207,9 @@ async fn get_pods_status(namespace: &str) -> Result Result, kube::Error> { +async fn get_services_status(namespace: &str) -> Result, CliError> { match connect_to_client().await { Ok(_) => { let output = Command::new(BASE_COMMAND) @@ -240,12 +242,14 @@ async fn get_services_status( } Err(_) => { Err( - 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, - }) + 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, + }) + ) ) } } diff --git a/cli/src/uninstall.rs b/cli/src/uninstall.rs index 0e55594..750c608 100644 --- a/cli/src/uninstall.rs +++ b/cli/src/uninstall.rs @@ -3,7 +3,7 @@ use colored::Colorize; use std::{ io::stdin, process::Command, time::Duration, thread }; use tracing::debug; -use crate::essential::{ BASE_COMMAND, InstallationError, connect_to_client }; +use crate::essential::{ BASE_COMMAND, CliError, connect_to_client }; use kube::{ Error, core::ErrorResponse }; //docs: @@ -15,9 +15,9 @@ use kube::{ Error, core::ErrorResponse }; // - read the user input (e.g. 1 > all components) // - uninstall the selected component or the whole namespace // -// Returns an InstallationError if something fails +// Returns an CliError if something fails -pub async fn uninstall() -> Result<(), InstallationError> { +pub async fn uninstall() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Uninstalling cortexflow..."); @@ -36,7 +36,7 @@ pub async fn uninstall() -> Result<(), InstallationError> { } Err(_) => { Err( - InstallationError::ClientError( + CliError::ClientError( Error::Api(ErrorResponse { status: "failed".to_string(), message: "Failed to connect to kubernetes client".to_string(), @@ -65,16 +65,16 @@ fn display_uninstall_options() { // - connects to kubernetes client // - execute the command to uninstall the cortexflow namespace // -// Returns an InstallationError if something fails +// Returns an CliError if something fails -async fn uninstall_all() -> Result<(), InstallationError> { +async fn uninstall_all() -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { println!("{} {}", "=====>".blue().bold(), "Deleting cortexflow components".red()); let output = Command::new(BASE_COMMAND) .args(["delete", "namespace", "cortexflow"]) .output() - .map_err(|e| InstallationError::InstallerError { + .map_err(|e| CliError::InstallerError { reason: format!("Failed to execute delete command: {}", e), })?; @@ -84,14 +84,14 @@ async fn uninstall_all() -> Result<(), InstallationError> { } else { let stderr = String::from_utf8_lossy(&output.stderr); eprintln!("Error deleting cortexflow namespace. Error: {} ", stderr); - Err(InstallationError::InstallerError { + Err(CliError::InstallerError { reason: format!("Failed to delete cortexflow namespace. Error: {}", stderr), }) } } Err(_) => { Err( - InstallationError::ClientError( + CliError::ClientError( Error::Api(ErrorResponse { status: "failed".to_string(), message: "Failed to connect to kubernetes client".to_string(), @@ -116,7 +116,7 @@ async fn uninstall_all() -> Result<(), InstallationError> { async fn uninstall_component( component_type: &str, component: &str -) -> Result<(), InstallationError> { +) -> Result<(), CliError> { match connect_to_client().await { Ok(_) => { println!("{} {} {}", "=====>".blue().bold(), "Deleting service", component); @@ -124,7 +124,7 @@ async fn uninstall_component( let output = Command::new(BASE_COMMAND) .args(["delete", component_type, component, "-n", "cortexflow"]) .output() - .map_err(|e| InstallationError::InstallerError { + .map_err(|e| CliError::InstallerError { reason: format!("Failed to execute delete command: {}", e), })?; @@ -134,14 +134,14 @@ async fn uninstall_component( } else { let stderr = String::from_utf8_lossy(&output.stderr); eprintln!("Error deleting {}:\n{}", component, stderr); - Err(InstallationError::InstallerError { + Err(CliError::InstallerError { reason: format!("Failed to delete component '{}': {}", component, stderr), }) } } Err(_) => { Err( - InstallationError::ClientError( + CliError::ClientError( Error::Api(ErrorResponse { status: "failed".to_string(), message: "Failed to connect to kubernetes client".to_string(), From 756c5f4ba5484f1d922485b14cd684a948b43322 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 7 Nov 2025 14:20:45 +0100 Subject: [PATCH 11/13] [Code Refactoring]: added custom error handling in main.rs. Removed unused imports. Fixed typos in return types --- cli/src/essential.rs | 2 +- cli/src/install.rs | 31 +++------ cli/src/main.rs | 158 +++++++++++++++++++++++-------------------- cli/src/uninstall.rs | 2 - 4 files changed, 97 insertions(+), 96 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 4c89c82..6645355 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -6,7 +6,7 @@ use colored::Colorize; use k8s_openapi::api::core::v1::ConfigMap; use k8s_openapi::serde_json::json; -use kube::{ Config, Error }; +use kube::{Error }; use kube::api::{ Api, ObjectMeta, Patch, PatchParams, PostParams }; use kube::client::Client; diff --git a/cli/src/install.rs b/cli/src/install.rs index bb97f5b..644212d 100644 --- a/cli/src/install.rs +++ b/cli/src/install.rs @@ -1,18 +1,9 @@ use colored::Colorize; -use kube::{ Client, core::ErrorResponse }; -use tracing::debug; +use kube::{ core::ErrorResponse }; use clap::{ Args, Subcommand, command }; -use std::{ process::{ Command, exit }, fmt, thread, time::Duration }; +use std::{ process::{ Command }, thread, time::Duration }; use crate::{ - essential::{ - connect_to_client, - create_config_file, - create_configs, - read_configs, - BASE_COMMAND, - CliError, - }, - install, + essential::{ connect_to_client, create_config_file, create_configs, BASE_COMMAND, CliError }, }; use kube::Error; @@ -59,7 +50,7 @@ pub struct InstallArgs { // main cortexflow installation function to install all the cortexflow components: // This function creates the cortexflow namespace, manages the metadata file creation and removes the temporary installation files -pub async fn install_cortexflow() { +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()); @@ -69,8 +60,9 @@ pub async fn install_cortexflow() { .expect("Failed to create cortexflow namespace"); let metadata_configs = create_configs(); - create_config_file(metadata_configs).await; - install_cluster_components(); + create_config_file(metadata_configs).await?; + install_cluster_components().await?; + Ok(()) } // docs: @@ -78,9 +70,10 @@ pub async fn install_cortexflow() { // main cortexflow installation function to install the examples: // This function installs the demostration examples -pub fn install_simple_example() { +pub async fn install_simple_example() -> Result<(), CliError> { println!("{} {}", "=====>".blue().bold(), "Installing simple example".white()); - install_simple_example_component(); + install_simple_example_component().await?; + Ok(()) } //docs: @@ -285,9 +278,7 @@ fn apply_component(file: &str) -> Result<(), CliError> { // // Returns an CliError if something fails -fn download_installation_files( - installation_files: InstallationType -) -> Result<(), CliError> { +fn download_installation_files(installation_files: InstallationType) -> Result<(), CliError> { match installation_files { InstallationType::Components(files) => { for src in files.iter() { diff --git a/cli/src/main.rs b/cli/src/main.rs index ada7b74..2a835ea 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,5 +1,4 @@ #![allow(warnings)] -//TODO: add an example with test pods during installation mod essential; mod install; mod logs; @@ -10,30 +9,34 @@ mod status; mod uninstall; use clap::command; -use clap::{Args, Error, Parser, Subcommand}; +use clap::{ Args, Parser, Subcommand }; use colored::Colorize; use std::result::Result::Ok; -use std::string; use tracing::debug; -use crate::essential::{info, read_configs, update_cli}; -use crate::install::{InstallArgs, InstallCommands, install_cortexflow, install_simple_example}; -use crate::logs::{LogsArgs, logs_command}; -use crate::monitoring::{MonitorArgs, MonitorCommands, list_features, monitor_identity_events}; -use crate::policies::{PoliciesArgs, PoliciesCommands, check_blocklist, create_blocklist, remove_ip}; -use crate::service::{ServiceArgs, ServiceCommands, describe_service, list_services}; -use crate::status::{StatusArgs, status_command}; +use crate::essential::{ CliError, info, update_cli }; +use crate::install::{ InstallArgs, InstallCommands, install_cortexflow, install_simple_example }; +use crate::logs::{ LogsArgs, logs_command }; +use crate::monitoring::{ MonitorArgs, MonitorCommands, list_features, monitor_identity_events }; +use crate::policies::{ + PoliciesArgs, + PoliciesCommands, + check_blocklist, + create_blocklist, + remove_ip, +}; +use crate::service::{ ServiceArgs, ServiceCommands, describe_service, list_services }; +use crate::status::{ StatusArgs, status_command }; use crate::uninstall::uninstall; -use crate::essential::GeneralData; use crate::essential::update_config_metadata; #[derive(Parser, Debug)] #[command( - author = GeneralData::AUTHOR, - version = GeneralData::VERSION, - about = None, - long_about = None + author = env!("CARGO_PKG_AUTHORS"), + version = env!("CARGO_PKG_VERSION"), + about = env!("CARGO_PKG_DESCRIPTION"), + long_about = env!("CARGO_PKG_DESCRIPTION") )] struct Cli { //name: String, @@ -44,9 +47,6 @@ struct Cli { #[derive(Subcommand, Debug, Clone)] enum Commands { /* list of available commands */ - #[command(name = "set-env")] SetEnv(SetArgs), - #[command(name = "get-env")] - GetEnv, #[command(name = "install", about = "Manage installation")] Install(InstallArgs), #[command(name = "uninstall", about = "Manage uninstallation")] Uninstall, @@ -65,70 +65,75 @@ struct SetArgs { val: String, } -async fn args_parser(){ +async fn args_parser() -> Result<(), CliError> { let args = Cli::parse(); - let env = "kubernetes".to_string(); - let general_data = GeneralData::new(env); debug!("Arguments {:?}", args.cmd); match args.cmd { - Some(Commands::SetEnv(env)) => { - general_data.set_env(env.val); - } - Some(Commands::GetEnv) => { - general_data.get_env_output(); - } - Some(Commands::Install(installation_args)) => match installation_args.install_cmd { - InstallCommands::All => { - install_cortexflow().await; - } - InstallCommands::TestPods => { - install_simple_example(); + Some(Commands::Install(installation_args)) => + match installation_args.install_cmd { + InstallCommands::All => { + install_cortexflow().await?; + Ok(()) + } + InstallCommands::TestPods => { + install_simple_example().await?; + Ok(()) + } } - }, Some(Commands::Uninstall) => { - uninstall(); + uninstall().await?; + Ok(()) } Some(Commands::Update) => { update_cli(); + Ok(()) } Some(Commands::Info) => { - info(general_data); + info(); + Ok(()) } - Some(Commands::Service(service_args)) => match service_args.service_cmd { - ServiceCommands::List { namespace } => { - Some(list_services(namespace)); - } - ServiceCommands::Describe { - service_name, - namespace, - } => { - describe_service(service_name, &namespace); + Some(Commands::Service(service_args)) => + match service_args.service_cmd { + ServiceCommands::List { namespace } => { + list_services(namespace).await?; + Ok(()) + } + ServiceCommands::Describe { service_name, namespace } => { + describe_service(service_name, &namespace).await?; + Ok(()) + } } - }, Some(Commands::Status(status_args)) => { - status_command(status_args.output, status_args.namespace); + status_command(status_args.output, status_args.namespace).await?; + Ok(()) } Some(Commands::Logs(logs_args)) => { - logs_command(logs_args.service, logs_args.component, logs_args.namespace); + logs_command(logs_args.service, logs_args.component, logs_args.namespace).await?; + Ok(()) } - Some(Commands::Monitor(monitor_args)) => match monitor_args.monitor_cmd { - MonitorCommands::List => { - let _ = list_features().await; - } - MonitorCommands::Connections => { - let _ = monitor_identity_events().await; + Some(Commands::Monitor(monitor_args)) => + match monitor_args.monitor_cmd { + MonitorCommands::List => { + let _ = list_features().await?; + Ok(()) + } + MonitorCommands::Connections => { + let _ = monitor_identity_events().await?; + Ok(()) + } } - }, Some(Commands::Policies(policies_args)) => { match policies_args.policy_cmd { PoliciesCommands::CheckBlocklist => { - let _ = check_blocklist().await; + let _ = check_blocklist().await?; + Ok(()) } PoliciesCommands::CreateBlocklist => { // pass the ip as a monitoring flag match policies_args.flags { None => { println!("{}", "Insert at least one ip to create a blocklist".red()); + Ok(()) } Some(ip) => { println!("inserted ip: {} ", ip); @@ -136,38 +141,45 @@ async fn args_parser(){ match create_blocklist(&ip).await { Ok(_) => { //update the config metadata - let _ = update_config_metadata(&ip, "add").await; + let _ = update_config_metadata(&ip, "add").await?; + Ok(()) } Err(e) => { println!("{}", e); + Ok(()) } } } } } - PoliciesCommands::RemoveIpFromBlocklist => match policies_args.flags { - None => { - println!( - "{}", - "Insert at least one ip to remove from the blocklist".red() - ); - } - Some(ip) => { - println!("Inserted ip: {}", ip); - match remove_ip(&ip).await { - Ok(_) => { - let _ = update_config_metadata(&ip, "delete").await; - } - Err(e) => { - println!("{}", e); + PoliciesCommands::RemoveIpFromBlocklist => + match policies_args.flags { + None => { + println!( + "{}", + "Insert at least one ip to remove from the blocklist".red() + ); + Ok(()) + } + Some(ip) => { + println!("Inserted ip: {}", ip); + match remove_ip(&ip).await { + Ok(_) => { + let _ = update_config_metadata(&ip, "delete").await?; + Ok(()) + } + Err(e) => { + println!("{}", e); + Ok(()) + } } } } - }, } } None => { eprintln!("CLI unknown argument. Cli arguments passed: {:?}", args.cmd); + Ok(()) } } } diff --git a/cli/src/uninstall.rs b/cli/src/uninstall.rs index 750c608..c28cff5 100644 --- a/cli/src/uninstall.rs +++ b/cli/src/uninstall.rs @@ -1,7 +1,5 @@ -use crate::{ essential::read_configs, status }; use colored::Colorize; use std::{ io::stdin, process::Command, time::Duration, thread }; -use tracing::debug; use crate::essential::{ BASE_COMMAND, CliError, connect_to_client }; use kube::{ Error, core::ErrorResponse }; From 8a5f3f7e30371b9cd8070e9f0dd0edc49c0e5345 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 7 Nov 2025 18:37:06 +0100 Subject: [PATCH 12/13] [Code refactoring]: Added documentation for CliError types. Added AgentError type and MonitoringError types. Added From trait implermentation to convert anyhow::Error into CliError. Cargo.lock update --- cli/src/essential.rs | 98 +++++++++++++++++++++++++++++++------------- core/Cargo.lock | 1 - 2 files changed, 70 insertions(+), 29 deletions(-) diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 6645355..7af6f3b 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -1,3 +1,4 @@ +use std::fmt::format; use std::{ collections::BTreeMap, fmt, process::Command, result::Result::Ok }; use kube::core::ErrorResponse; @@ -6,7 +7,6 @@ use colored::Colorize; use k8s_openapi::api::core::v1::ConfigMap; use k8s_openapi::serde_json::json; -use kube::{Error }; use kube::api::{ Api, ObjectMeta, Patch, PatchParams, PostParams }; use kube::client::Client; @@ -14,9 +14,33 @@ pub static BASE_COMMAND: &str = "kubectl"; // docs: Kubernetes base command // docs: // -// Custom enum definition to group all the installation error for cortexflow +// CliError enum to group all the errors // +// Custom error definition +// InstallerError: +// - used for general installation errors occured during the installation of cortexflow components. Can be used for: +// - Return downloading errors +// - Return unsuccessful file removal during installation +// +// ClientError: +// - used for Kubernetes client errors. Can be used for: +// - Return client connection errors +// +// UninstallError: +// - used for general installation errors occured during the uninstall for cortexflow components. Can be used for: +// - Return components removal errors +// +// AgentError: +// - used for cortexflow agent errors. Can be used for: +// - return errors from the reflection server +// - return unavailable agent errors (404) +// +// MonitoringError: +// - used for general monitoring errors. TODO: currently under implementation +// +// implements fmt::Display for user friendly error messages +#[derive(Debug)] pub enum CliError { InstallerError { reason: String, @@ -25,38 +49,56 @@ pub enum CliError { UninstallError { reason: String, }, + AgentError(tonic_reflection::server::Error), + MonitoringError { + reason: String, + }, } +// docs: +// error type conversions + impl From for CliError { - fn from(e: Error) -> Self { + fn from(e: kube::Error) -> Self { CliError::ClientError(e) } } +impl From for CliError { + fn from(e: anyhow::Error) -> Self { + CliError::MonitoringError { reason: format!("{}", e) } + } +} // docs: -// -// Custom error definition -// InstallerError: -// - used for general installation errors occured during the installation of cortexflow components. Can be used for: -// - Return downloading errors -// - Return unsuccessful file removal -// -// -// implements fmt::Display for user-friendly error messages -// - -#[derive(Debug, Clone)] -pub struct InstallerError { - pub(crate) reason: String, -} +// fmt::Display implementation for CliError type. Creates a user friendly message error message. +// TODO: implement colored messages using the colorize crate for better output display -impl fmt::Display for InstallerError { +impl fmt::Display for CliError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "An error occured while installing cortexflow components. Reason: {}", - self.reason - ); - Ok(()) + match self { + CliError::InstallerError { reason } => { + write!( + f, + "An error occured while installing cortexflow components. Reason: {}", + reason + ) + } + CliError::UninstallError { reason } => { + write!( + f, + "An error occured while installing cortexflow components. Reason: {}", + reason + ) + } + CliError::MonitoringError { reason } => { + write!( + f, + "An error occured while installing cortexflow components. Reason: {}", + reason + ) + } + CliError::ClientError(e) => write!(f, "Client Error: {}", e), + CliError::AgentError(e) => write!(f, "Agent Error: {}", e), + } } } @@ -167,7 +209,7 @@ pub async fn read_configs() -> Result, CliError> { Err(_) => { Err( CliError::ClientError( - Error::Api(ErrorResponse { + 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(), @@ -228,7 +270,7 @@ pub async fn create_config_file(config_struct: MetadataConfigFile) -> Result<(), Err(_) => { Err( CliError::ClientError( - Error::Api(ErrorResponse { + 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(), @@ -336,7 +378,7 @@ pub async fn update_configmap(config_struct: MetadataConfigFile) -> Result<(), C Err(_) => { Err( CliError::ClientError( - Error::Api(ErrorResponse { + 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(), diff --git a/core/Cargo.lock b/core/Cargo.lock index 4ff5da1..e599b50 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -391,7 +391,6 @@ dependencies = [ "kube", "libc", "nix", - "serde_yaml", "tokio", "tracing", "tracing-subscriber", From fda35e1c235a135d589291002f502d6ac9a85c31 Mon Sep 17 00:00:00 2001 From: LorenzoTettamanti Date: Fri, 7 Nov 2025 21:19:30 +0100 Subject: [PATCH 13/13] [Code Refactoring]: updated dependencies. Improved error handling in main.rs for better clarity --- cli/Cargo.lock | 35 +++++++++++++++++++---------- cli/Cargo.toml | 8 +++---- cli/src/essential.rs | 6 ++++- cli/src/main.rs | 53 ++++++++++++++------------------------------ 4 files changed, 49 insertions(+), 53 deletions(-) diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 3584cd8..6ef922e 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -78,9 +78,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.99" +version = "1.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0674a1ddeecb70197781e945de4b3b8ffb61fa939a5597bcf48503737663100" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" [[package]] name = "assert_matches" @@ -302,9 +302,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.45" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fc0e74a703892159f5ae7d3aac52c8e6c392f5ae5f359c70b5881d60aaac318" +checksum = "4c26d721170e0295f191a69bd9a1f93efcdb0aff38684b61ab5750468972e5f5" dependencies = [ "clap_builder", "clap_derive", @@ -312,9 +312,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.44" +version = "4.5.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3e7f4214277f3c7aa526a59dd3fbe306a370daee1f8b7b8c987069cd8e888a8" +checksum = "75835f0c7bf681bfd05abe44e965760fea999a5286c6eb2d59883634fd02011a" dependencies = [ "anstream", "anstyle", @@ -324,9 +324,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.45" +version = "4.5.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14cb31bb0a7d536caef2639baa7fad459e15c3144efefa6dbd1c84562c4739f6" +checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" dependencies = [ "heck", "proc-macro2", @@ -380,6 +380,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cortexbrain-common" +version = "0.1.0" +dependencies = [ + "anyhow", + "tracing", + "tracing-subscriber", +] + [[package]] name = "cortexflow-cli" version = "0.1.3" @@ -409,6 +418,7 @@ dependencies = [ "bytemuck", "bytemuck_derive", "chrono", + "cortexbrain-common", "cortexflow_identity", "prost", "tokio", @@ -432,6 +442,7 @@ dependencies = [ "bytemuck", "bytemuck_derive", "bytes", + "cortexbrain-common", "k8s-openapi", "kube", "libc", @@ -1885,9 +1896,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ac5a8627ada0968acec063a4746bf79588aa03ccb66db2f75d7dce26722a40" +checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" dependencies = [ "async-trait", "axum", @@ -1953,9 +1964,9 @@ dependencies = [ [[package]] name = "tonic-reflection" -version = "0.14.1" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0267a0073385cd94996197d12acb1856a3a0a2367482c726a48a769f6fed8a3a" +checksum = "34da53e8387581d66db16ff01f98a70b426b091fdf76856e289d5c1bd386ed7b" dependencies = [ "prost", "prost-types", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e5256f6..68533ef 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -11,15 +11,15 @@ license = "Apache-2.0" readme = "../README.md" [dependencies] -clap = { version = "4.5.38", features = ["derive"] } +clap = { version = "4.5.51", features = ["derive"] } colored = "3.0.0" directories = "6.0.0" serde = { version = "1.0.219", features = ["derive"] } tracing = "0.1.41" tokio = {version = "1.47.0",features = ["macros",'rt-multi-thread']} -anyhow = "1.0.98" -tonic = "0.14.1" -tonic-reflection = "0.14.1" +anyhow = "1.0.100" +tonic = "0.14.2" +tonic-reflection = "0.14.2" prost-types = "0.14.1" prost = "0.14.1" cortexflow_agent_api = {path = "../core/api",features = ["client"]} diff --git a/cli/src/essential.rs b/cli/src/essential.rs index 7af6f3b..1a4d44f 100644 --- a/cli/src/essential.rs +++ b/cli/src/essential.rs @@ -1,4 +1,3 @@ -use std::fmt::format; use std::{ collections::BTreeMap, fmt, process::Command, result::Result::Ok }; use kube::core::ErrorResponse; @@ -67,6 +66,11 @@ impl From for CliError { CliError::MonitoringError { reason: format!("{}", e) } } } +impl From<()> for CliError { + fn from (v: ()) -> Self{ + return ().into() + } +} // docs: // fmt::Display implementation for CliError type. Creates a user friendly message error message. diff --git a/cli/src/main.rs b/cli/src/main.rs index 2a835ea..fb49d09 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -8,7 +8,6 @@ mod service; mod status; mod uninstall; -use clap::command; use clap::{ Args, Parser, Subcommand }; use colored::Colorize; use std::result::Result::Ok; @@ -72,68 +71,55 @@ async fn args_parser() -> Result<(), CliError> { Some(Commands::Install(installation_args)) => match installation_args.install_cmd { InstallCommands::All => { - install_cortexflow().await?; - Ok(()) + install_cortexflow().await.map_err(|e| eprintln!("{}",e) )?; } InstallCommands::TestPods => { - install_simple_example().await?; - Ok(()) + install_simple_example().await.map_err(|e| eprintln!("{}",e) )?; } } Some(Commands::Uninstall) => { - uninstall().await?; - Ok(()) + uninstall().await.map_err(|e| eprintln!("{}",e) )?; } Some(Commands::Update) => { update_cli(); - Ok(()) } Some(Commands::Info) => { info(); - Ok(()) } Some(Commands::Service(service_args)) => match service_args.service_cmd { ServiceCommands::List { namespace } => { - list_services(namespace).await?; - Ok(()) + list_services(namespace).await.map_err(|e| eprintln!("{}",e) )?; } ServiceCommands::Describe { service_name, namespace } => { - describe_service(service_name, &namespace).await?; - Ok(()) + describe_service(service_name, &namespace).await.map_err(|e| eprintln!("{}",e) )?; } } Some(Commands::Status(status_args)) => { - status_command(status_args.output, status_args.namespace).await?; - Ok(()) + status_command(status_args.output, status_args.namespace).await.map_err(|e| eprintln!("{}",e) )?; } Some(Commands::Logs(logs_args)) => { - logs_command(logs_args.service, logs_args.component, logs_args.namespace).await?; - Ok(()) + logs_command(logs_args.service, logs_args.component, logs_args.namespace).await.map_err(|e| eprintln!("{}",e) )?; } Some(Commands::Monitor(monitor_args)) => match monitor_args.monitor_cmd { MonitorCommands::List => { - let _ = list_features().await?; - Ok(()) + let _ = list_features().await.map_err(|e| eprintln!("{}",e) )?; } MonitorCommands::Connections => { - let _ = monitor_identity_events().await?; - Ok(()) + let _ = monitor_identity_events().await.map_err(|e| eprintln!("{}",e) )?; } } Some(Commands::Policies(policies_args)) => { match policies_args.policy_cmd { PoliciesCommands::CheckBlocklist => { - let _ = check_blocklist().await?; - Ok(()) + let _ = check_blocklist().await.map_err(|e| eprintln!("{}",e) )?; } PoliciesCommands::CreateBlocklist => { // pass the ip as a monitoring flag match policies_args.flags { None => { - println!("{}", "Insert at least one ip to create a blocklist".red()); - Ok(()) + eprintln!("{}", "Insert at least one ip to create a blocklist".red()); } Some(ip) => { println!("inserted ip: {} ", ip); @@ -141,12 +127,10 @@ async fn args_parser() -> Result<(), CliError> { match create_blocklist(&ip).await { Ok(_) => { //update the config metadata - let _ = update_config_metadata(&ip, "add").await?; - Ok(()) + let _ = update_config_metadata(&ip, "add").await.map_err(|e| eprintln!("{}",e) )?; } Err(e) => { - println!("{}", e); - Ok(()) + eprintln!("{}", e); } } } @@ -155,22 +139,19 @@ async fn args_parser() -> Result<(), CliError> { PoliciesCommands::RemoveIpFromBlocklist => match policies_args.flags { None => { - println!( + eprintln!( "{}", "Insert at least one ip to remove from the blocklist".red() ); - Ok(()) } Some(ip) => { println!("Inserted ip: {}", ip); match remove_ip(&ip).await { Ok(_) => { - let _ = update_config_metadata(&ip, "delete").await?; - Ok(()) + let _ = update_config_metadata(&ip, "delete").await.map_err(|e| eprintln!("{}",e) )?; } Err(e) => { - println!("{}", e); - Ok(()) + eprintln!("{}", e); } } } @@ -179,9 +160,9 @@ async fn args_parser() -> Result<(), CliError> { } None => { eprintln!("CLI unknown argument. Cli arguments passed: {:?}", args.cmd); - Ok(()) } } + Ok(()) } #[tokio::main]