From 6e061eea167a5ceaf6f313da905a1cca695ef1d0 Mon Sep 17 00:00:00 2001 From: Pranav Verma Date: Wed, 18 Jun 2025 02:22:04 +0530 Subject: [PATCH 1/5] [#77] Auto Fetch CLI version from `cargo.toml` file --- cli/src/general.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/general.rs b/cli/src/general.rs index 2cdec27..56992d0 100644 --- a/cli/src/general.rs +++ b/cli/src/general.rs @@ -7,7 +7,7 @@ pub struct GeneralData { } impl GeneralData { - pub const VERSION: &str = "0.1"; + pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const AUTHOR: &str = "CortexFlow"; pub const DESCRIPTION: &str = ""; From a9186da61e6b9af03a5a451017443649566c8ab4 Mon Sep 17 00:00:00 2001 From: Pranav Verma Date: Wed, 18 Jun 2025 02:26:58 +0530 Subject: [PATCH 2/5] [#77] Implemented `service describe` subcommand. --- cli/src/main.rs | 12 +++++++++++- cli/src/service.rs | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index b6cdd28..b39abb8 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -11,7 +11,7 @@ use tracing::debug; use crate::essential::{ info, update_cli }; use crate::install::install_cortexflow; use crate::uninstall::uninstall; -use crate::service::list_services; +use crate::service::{list_services, describe_service}; use crate::general::GeneralData; @@ -65,6 +65,12 @@ enum ServiceCommands { #[arg(long)] namespace: Option, }, + #[command(name="describe")] + Describe { + service_name: String, + #[arg(long)] + namespace: Option, + }, } fn args_parser() -> Result<(), Error> { @@ -103,6 +109,10 @@ fn args_parser() -> Result<(), Error> { list_services(namespace); Ok(()) } + ServiceCommands::Describe { service_name, namespace } => { + describe_service(service_name, namespace); + Ok(()) + } } } None => { diff --git a/cli/src/service.rs b/cli/src/service.rs index 86c9645..82bdfe7 100644 --- a/cli/src/service.rs +++ b/cli/src/service.rs @@ -56,4 +56,42 @@ pub fn list_services(namespace: Option) { std::process::exit(1); } } +} + +pub fn describe_service(service_name: String, namespace: Option) { + let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); + + println!("Describing service '{}' in namespace: {}", service_name, ns); + println!("{}", "=".repeat(60)); + + // Execute kubectl describe pod command + let output = Command::new("kubectl") + .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); + } + + let stdout = str::from_utf8(&output.stdout).unwrap_or(""); + + if stdout.trim().is_empty() { + println!("No description found for pod '{}'", service_name); + return; + } + + // 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); + } + } } \ No newline at end of file From 640800e21f7e13f98153819c3ae57608d8a6cd1b Mon Sep 17 00:00:00 2001 From: Pranav Verma Date: Wed, 18 Jun 2025 02:38:21 +0530 Subject: [PATCH 3/5] [#77] Added `status` command to the CLI --- cli/src/main.rs | 14 ++++ cli/src/mod.rs | 3 +- cli/src/status.rs | 190 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 cli/src/status.rs diff --git a/cli/src/main.rs b/cli/src/main.rs index b39abb8..d5d44ec 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -3,6 +3,7 @@ mod install; mod general; mod uninstall; mod service; +mod status; use clap::{ Error, Parser, Subcommand, Args }; use clap::command; @@ -12,6 +13,7 @@ use crate::essential::{ info, update_cli }; use crate::install::install_cortexflow; use crate::uninstall::uninstall; use crate::service::{list_services, describe_service}; +use crate::status::status_command; use crate::general::GeneralData; @@ -46,6 +48,8 @@ enum Commands { Info, #[command(name="service")] Service(ServiceArgs), + #[command(name="status")] + Status(StatusArgs), } #[derive(Args, Debug, Clone)] struct SetArgs { @@ -73,6 +77,12 @@ enum ServiceCommands { }, } +#[derive(Args, Debug, Clone)] +struct StatusArgs { + #[arg(long, value_enum)] + output: Option, +} + fn args_parser() -> Result<(), Error> { let args = Cli::parse(); let env = args.env; @@ -115,6 +125,10 @@ fn args_parser() -> Result<(), Error> { } } } + Some(Commands::Status(status_args)) => { + status_command(status_args.output); + Ok(()) + } None => { eprintln!("CLI unknown argument. Cli arguments passed: {:?}", args.cmd); Ok(()) diff --git a/cli/src/mod.rs b/cli/src/mod.rs index db6f1c3..8d3651a 100644 --- a/cli/src/mod.rs +++ b/cli/src/mod.rs @@ -2,4 +2,5 @@ pub mod essential; pub mod install; pub mod general; pub mod uninstall; -pub mod service; \ No newline at end of file +pub mod service; +pub mod status; \ No newline at end of file diff --git a/cli/src/status.rs b/cli/src/status.rs new file mode 100644 index 0000000..382f7e4 --- /dev/null +++ b/cli/src/status.rs @@ -0,0 +1,190 @@ +use std::process::Command; +use std::str; + +#[derive(Debug)] +pub enum OutputFormat { + Text, + Json, + Yaml, +} + +impl From for OutputFormat { + fn from(s: String) -> Self { + match s.to_lowercase().as_str() { + "json" => OutputFormat::Json, + "yaml" => OutputFormat::Yaml, + _ => OutputFormat::Text, + } + } +} + +pub fn status_command(output_format: Option) { + let format = output_format.map(OutputFormat::from).unwrap_or(OutputFormat::Text); + + println!("Checking CortexFlow status..."); + + // namespace checking + let namespace_status = check_namespace_exists("cortexflow"); + + // get pods + let pods_status = get_pods_status("cortexflow"); + + // get services + let services_status = get_services_status("cortexflow"); + + // display options (format) + match format { + OutputFormat::Text => display_text_format(namespace_status, pods_status, services_status), + OutputFormat::Json => display_json_format(namespace_status, pods_status, services_status), + OutputFormat::Yaml => display_yaml_format(namespace_status, pods_status, services_status), + } +} + +fn check_namespace_exists(namespace: &str) -> bool { + let output = Command::new("kubectl") + .args(["get", "namespace", namespace]) + .output(); + + match output { + Ok(output) => output.status.success(), + Err(_) => false, + } +} + +fn get_pods_status(namespace: &str) -> Vec<(String, String, String)> { + let output = Command::new("kubectl") + .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() + } + _ => Vec::new(), + } +} + +fn get_services_status(namespace: &str) -> Vec<(String, String, String)> { + let output = Command::new("kubectl") + .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() + } + _ => Vec::new(), + } +} + +fn display_text_format(namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { + println!("\nšŸ” CortexFlow Status Report"); + println!("{}", "=".repeat(50)); + + println!("\nšŸ“¦ Namespace Status:"); + if namespace_exists { + println!(" āœ… cortexflow namespace: EXISTS"); + } else { + println!(" āŒ cortexflow namespace: NOT FOUND"); + } + + println!("\nšŸš€ Pods Status:"); + if pods.is_empty() { + println!(" āš ļø No pods found in cortexflow namespace"); + } else { + for (name, ready, status) in pods { + let icon = if status == "Running" { "āœ…" } else { "āš ļø" }; + println!(" {} {}: {} ({})", icon, name, status, ready); + } + } + + println!("\n🌐 Services Status:"); + if services.is_empty() { + println!(" āš ļø No services found in cortexflow namespace"); + } else { + for (name, service_type, cluster_ip) in services { + println!(" šŸ”— {}: {} ({})", name, service_type, cluster_ip); + } + } + + println!("\n{}", "=".repeat(50)); +} + +fn display_json_format(namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { + println!("{{"); + println!(" \"namespace\": {{"); + println!(" \"name\": \"cortexflow\","); + println!(" \"exists\": {}", namespace_exists); + println!(" }},"); + + println!(" \"pods\": ["); + for (i, (name, ready, status)) in pods.iter().enumerate() { + let comma = if i == pods.len() - 1 { "" } else { "," }; + println!(" {{"); + println!(" \"name\": \"{}\",", name); + println!(" \"ready\": \"{}\",", ready); + println!(" \"status\": \"{}\"", status); + println!(" }}{}", comma); + } + println!(" ],"); + + println!(" \"services\": ["); + for (i, (name, service_type, cluster_ip)) in services.iter().enumerate() { + let comma = if i == services.len() - 1 { "" } else { "," }; + println!(" {{"); + println!(" \"name\": \"{}\",", name); + println!(" \"type\": \"{}\",", service_type); + println!(" \"cluster_ip\": \"{}\"", cluster_ip); + println!(" }}{}", comma); + } + println!(" ]"); + println!("}}"); +} + +fn display_yaml_format(namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { + println!("namespace:"); + println!(" name: cortexflow"); + println!(" exists: {}", namespace_exists); + + println!("pods:"); + for (name, ready, status) in pods { + println!(" - name: {}", name); + println!(" ready: {}", ready); + println!(" status: {}", status); + } + + println!("services:"); + for (name, service_type, cluster_ip) in services { + println!(" - name: {}", name); + println!(" type: {}", service_type); + println!(" cluster_ip: {}", cluster_ip); + } +} \ No newline at end of file From 292c9caae649680abc5bef706d76f29793c0753c Mon Sep 17 00:00:00 2001 From: Pranav Verma Date: Wed, 18 Jun 2025 16:46:13 +0530 Subject: [PATCH 4/5] [#77] Enhance status command to support namespace argument and improve output formatting --- cli/src/main.rs | 6 ++- cli/src/status.rs | 102 +++++++++++++++++++++++++++++++++++++--------- 2 files changed, 86 insertions(+), 22 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index d5d44ec..6fa3d4d 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -79,8 +79,10 @@ enum ServiceCommands { #[derive(Args, Debug, Clone)] struct StatusArgs { - #[arg(long, value_enum)] + #[arg(long)] output: Option, + #[arg(long)] + namespace: Option, } fn args_parser() -> Result<(), Error> { @@ -126,7 +128,7 @@ fn args_parser() -> Result<(), Error> { } } Some(Commands::Status(status_args)) => { - status_command(status_args.output); + status_command(status_args.output, status_args.namespace); Ok(()) } None => { diff --git a/cli/src/status.rs b/cli/src/status.rs index 382f7e4..ebeb585 100644 --- a/cli/src/status.rs +++ b/cli/src/status.rs @@ -18,25 +18,70 @@ impl From for OutputFormat { } } -pub fn status_command(output_format: 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!("Checking CortexFlow status..."); + println!("Checking CortexFlow status for namespace: {}", ns); // namespace checking - let namespace_status = check_namespace_exists("cortexflow"); - - // get pods - let pods_status = get_pods_status("cortexflow"); + 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); + } + } + } + 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 services - let services_status = get_services_status("cortexflow"); + // 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(namespace_status, pods_status, services_status), - OutputFormat::Json => display_json_format(namespace_status, pods_status, services_status), - OutputFormat::Yaml => display_yaml_format(namespace_status, pods_status, services_status), + 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), } } @@ -51,6 +96,23 @@ fn check_namespace_exists(namespace: &str) -> bool { } } +fn get_available_namespaces() -> Vec { + let output = Command::new("kubectl") + .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(), + } +} + fn get_pods_status(namespace: &str) -> Vec<(String, String, String)> { let output = Command::new("kubectl") .args(["get", "pods", "-n", namespace, "--no-headers"]) @@ -105,20 +167,20 @@ fn get_services_status(namespace: &str) -> Vec<(String, String, String)> { } } -fn display_text_format(namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { +fn display_text_format(ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { println!("\nšŸ” CortexFlow Status Report"); println!("{}", "=".repeat(50)); println!("\nšŸ“¦ Namespace Status:"); if namespace_exists { - println!(" āœ… cortexflow namespace: EXISTS"); + println!(" āœ… {} namespace: EXISTS", ns); } else { - println!(" āŒ cortexflow namespace: NOT FOUND"); + println!(" āŒ {} namespace: NOT FOUND", ns); } println!("\nšŸš€ Pods Status:"); if pods.is_empty() { - println!(" āš ļø No pods found in cortexflow namespace"); + println!(" āš ļø No pods found in {} namespace", ns); } else { for (name, ready, status) in pods { let icon = if status == "Running" { "āœ…" } else { "āš ļø" }; @@ -128,7 +190,7 @@ fn display_text_format(namespace_exists: bool, pods: Vec<(String, String, String println!("\n🌐 Services Status:"); if services.is_empty() { - println!(" āš ļø No services found in cortexflow namespace"); + println!(" āš ļø No services found in {} namespace", ns); } else { for (name, service_type, cluster_ip) in services { println!(" šŸ”— {}: {} ({})", name, service_type, cluster_ip); @@ -138,10 +200,10 @@ fn display_text_format(namespace_exists: bool, pods: Vec<(String, String, String println!("\n{}", "=".repeat(50)); } -fn display_json_format(namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { +fn display_json_format(ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { println!("{{"); println!(" \"namespace\": {{"); - println!(" \"name\": \"cortexflow\","); + println!(" \"name\": \"{}\",", ns); println!(" \"exists\": {}", namespace_exists); println!(" }},"); @@ -169,9 +231,9 @@ fn display_json_format(namespace_exists: bool, pods: Vec<(String, String, String println!("}}"); } -fn display_yaml_format(namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { +fn display_yaml_format(ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { println!("namespace:"); - println!(" name: cortexflow"); + println!(" name: {}", ns); println!(" exists: {}", namespace_exists); println!("pods:"); From dcac5a571a0ab5891a6b85deb833fd4234979f81 Mon Sep 17 00:00:00 2001 From: Pranav Verma Date: Wed, 18 Jun 2025 16:55:08 +0530 Subject: [PATCH 5/5] [#77] Add namespace existence check and list available namespaces in service commands --- cli/src/service.rs | 67 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/cli/src/service.rs b/cli/src/service.rs index 82bdfe7..8ab8707 100644 --- a/cli/src/service.rs +++ b/cli/src/service.rs @@ -1,11 +1,58 @@ use std::process::Command; use std::str; +fn check_namespace_exists(namespace: &str) -> bool { + let output = Command::new("kubectl") + .args(["get", "namespace", namespace]) + .output(); + + match output { + Ok(output) => output.status.success(), + Err(_) => false, + } +} + +fn get_available_namespaces() -> Vec { + let output = Command::new("kubectl") + .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(), + } +} + pub fn list_services(namespace: Option) { let ns = namespace.unwrap_or_else(|| "cortexflow".to_string()); println!("Listing services in namespace: {}", ns); + // 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); + } + } else { + println!("No namespaces found in the cluster."); + } + + std::process::exit(1); + } + // kubectl command to get services let output = Command::new("kubectl") .args(["get", "pods", "-n", &ns, "--no-headers"]) @@ -64,6 +111,26 @@ pub fn describe_service(service_name: String, namespace: Option) { println!("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("kubectl") .args(["describe", "pod", &service_name, "-n", &ns])