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 = ""; diff --git a/cli/src/main.rs b/cli/src/main.rs index b6cdd28..6fa3d4d 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; @@ -11,7 +12,8 @@ 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::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 { @@ -65,6 +69,20 @@ enum ServiceCommands { #[arg(long)] namespace: Option, }, + #[command(name="describe")] + Describe { + service_name: String, + #[arg(long)] + namespace: Option, + }, +} + +#[derive(Args, Debug, Clone)] +struct StatusArgs { + #[arg(long)] + output: Option, + #[arg(long)] + namespace: Option, } fn args_parser() -> Result<(), Error> { @@ -103,8 +121,16 @@ fn args_parser() -> Result<(), Error> { 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(()) + } 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/service.rs b/cli/src/service.rs index 86c9645..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"]) @@ -56,4 +103,62 @@ 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)); + + // 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]) + .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 diff --git a/cli/src/status.rs b/cli/src/status.rs new file mode 100644 index 0000000..ebeb585 --- /dev/null +++ b/cli/src/status.rs @@ -0,0 +1,252 @@ +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, 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 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); + } + } + } + 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 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(), + } +} + +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(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!(" āœ… {} namespace: EXISTS", ns); + } else { + println!(" āŒ {} namespace: NOT FOUND", ns); + } + + println!("\nšŸš€ Pods Status:"); + if pods.is_empty() { + println!(" āš ļø No pods found in {} namespace", ns); + } 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 {} namespace", ns); + } else { + for (name, service_type, cluster_ip) in services { + println!(" šŸ”— {}: {} ({})", name, service_type, cluster_ip); + } + } + + println!("\n{}", "=".repeat(50)); +} + +fn display_json_format(ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { + println!("{{"); + println!(" \"namespace\": {{"); + println!(" \"name\": \"{}\",", ns); + 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(ns: &str, namespace_exists: bool, pods: Vec<(String, String, String)>, services: Vec<(String, String, String)>) { + println!("namespace:"); + println!(" name: {}", ns); + 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