From 99e121b0d8084ada6095f9c6a3666501619d46eb Mon Sep 17 00:00:00 2001 From: Mahmoud-Emad Date: Tue, 8 Jul 2025 16:37:10 +0300 Subject: [PATCH 1/2] feat: Providing some clusters for kubernetes --- examples/kubernetes/clusters/generic.rs | 97 ++++++++++++++++++++++ examples/kubernetes/clusters/postgres.rhai | 45 ++++++++++ examples/kubernetes/clusters/postgres.rs | 73 ++++++++++++++++ examples/kubernetes/clusters/redis.rhai | 44 ++++++++++ examples/kubernetes/clusters/redis.rs | 72 ++++++++++++++++ kubernetes/examples/generic.rs | 97 ++++++++++++++++++++++ kubernetes/examples/postgres.rs | 73 ++++++++++++++++ kubernetes/examples/redis.rs | 72 ++++++++++++++++ kubernetes/src/kubernetes_manager.rs | 65 +++++++++++++++ kubernetes/src/rhai.rs | 41 +++++++++ 10 files changed, 679 insertions(+) create mode 100644 examples/kubernetes/clusters/generic.rs create mode 100644 examples/kubernetes/clusters/postgres.rhai create mode 100644 examples/kubernetes/clusters/postgres.rs create mode 100644 examples/kubernetes/clusters/redis.rhai create mode 100644 examples/kubernetes/clusters/redis.rs create mode 100644 kubernetes/examples/generic.rs create mode 100644 kubernetes/examples/postgres.rs create mode 100644 kubernetes/examples/redis.rs diff --git a/examples/kubernetes/clusters/generic.rs b/examples/kubernetes/clusters/generic.rs new file mode 100644 index 0000000..94b7c66 --- /dev/null +++ b/examples/kubernetes/clusters/generic.rs @@ -0,0 +1,97 @@ +//! Generic Application Deployment Example +//! +//! This example shows how to deploy any containerized application using the +//! KubernetesManager convenience methods. This works for any Docker image. + +use sal_kubernetes::KubernetesManager; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create Kubernetes manager + let km = KubernetesManager::new("default").await?; + + // Example 1: Simple web server deployment + println!("=== Example 1: Simple Nginx Web Server ==="); + + km.deploy_application("web-server", "nginx:latest", 2, 80, None) + .await?; + println!("āœ… Nginx web server deployed!"); + + // Example 2: Node.js application with labels + println!("\n=== Example 2: Node.js Application ==="); + + let mut node_labels = HashMap::new(); + node_labels.insert("app".to_string(), "node-app".to_string()); + node_labels.insert("tier".to_string(), "backend".to_string()); + node_labels.insert("environment".to_string(), "production".to_string()); + + km.deploy_application( + "node-app", // name + "node:18-alpine", // image + 3, // replicas - scale to 3 instances + 3000, // port + Some(node_labels), // labels + ) + .await?; + + println!("āœ… Node.js application deployed!"); + + // Example 3: Database deployment (any database) + println!("\n=== Example 3: MongoDB Database ==="); + + let mut mongo_labels = HashMap::new(); + mongo_labels.insert("app".to_string(), "mongodb".to_string()); + mongo_labels.insert("type".to_string(), "database".to_string()); + mongo_labels.insert("engine".to_string(), "mongodb".to_string()); + + km.deploy_application( + "mongodb", // name + "mongo:6.0", // image + 1, // replicas - single instance for simplicity + 27017, // port + Some(mongo_labels), // labels + ) + .await?; + + println!("āœ… MongoDB deployed!"); + + // Check status of all deployments + println!("\n=== Checking Deployment Status ==="); + + let deployments = km.deployments_list().await?; + + for deployment in &deployments { + if let Some(name) = &deployment.metadata.name { + let total_replicas = deployment + .spec + .as_ref() + .and_then(|s| s.replicas) + .unwrap_or(0); + let ready_replicas = deployment + .status + .as_ref() + .and_then(|s| s.ready_replicas) + .unwrap_or(0); + + println!( + "{}: {}/{} replicas ready", + name, ready_replicas, total_replicas + ); + } + } + + println!("\nšŸŽ‰ All deployments completed!"); + println!("\nšŸ’” Key Points:"); + println!(" • Any Docker image can be deployed using this simple interface"); + println!(" • Use labels to organize and identify your applications"); + println!( + " • The same method works for databases, web servers, APIs, and any containerized app" + ); + println!(" • For advanced configuration, use the individual KubernetesManager methods"); + println!( + " • Environment variables and resource limits can be added via direct Kubernetes API" + ); + + Ok(()) +} diff --git a/examples/kubernetes/clusters/postgres.rhai b/examples/kubernetes/clusters/postgres.rhai new file mode 100644 index 0000000..75cd9bf --- /dev/null +++ b/examples/kubernetes/clusters/postgres.rhai @@ -0,0 +1,45 @@ +//! PostgreSQL Cluster Deployment Example (Rhai) +//! +//! This script shows how to deploy a PostgreSQL cluster using Rhai scripting +//! with the KubernetesManager convenience methods. + +print("=== PostgreSQL Cluster Deployment ==="); + +// Create Kubernetes manager for the database namespace +print("Creating Kubernetes manager for 'database' namespace..."); +let km = kubernetes_manager_new("database"); +print("āœ“ Kubernetes manager created"); + +// Create PostgreSQL cluster using the convenience method +print("\nDeploying PostgreSQL cluster..."); + +try { + // Deploy PostgreSQL using the convenience method + let result = deploy_application(km, "postgres-cluster", "postgres:15", 2, 5432, #{ + "app": "postgres-cluster", + "type": "database", + "engine": "postgresql" + }); + print("āœ“ " + result); + + print("\nāœ… PostgreSQL cluster deployed successfully!"); + + print("\nšŸ“‹ Connection Information:"); + print(" Host: postgres-cluster.database.svc.cluster.local"); + print(" Port: 5432"); + print(" Database: postgres (default)"); + print(" Username: postgres (default)"); + + print("\nšŸ”§ To connect from another pod:"); + print(" psql -h postgres-cluster.database.svc.cluster.local -U postgres"); + + print("\nšŸ’” Next steps:"); + print(" • Set POSTGRES_PASSWORD environment variable"); + print(" • Configure persistent storage"); + print(" • Set up backup and monitoring"); + +} catch(e) { + print("āŒ Failed to deploy PostgreSQL cluster: " + e); +} + +print("\n=== Deployment Complete ==="); diff --git a/examples/kubernetes/clusters/postgres.rs b/examples/kubernetes/clusters/postgres.rs new file mode 100644 index 0000000..dbbd389 --- /dev/null +++ b/examples/kubernetes/clusters/postgres.rs @@ -0,0 +1,73 @@ +//! PostgreSQL Cluster Deployment Example +//! +//! This example shows how to deploy a PostgreSQL cluster using the +//! KubernetesManager convenience methods. + +use sal_kubernetes::KubernetesManager; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create Kubernetes manager for the database namespace + let km = KubernetesManager::new("database").await?; + + // Configure PostgreSQL-specific labels + let mut labels = HashMap::new(); + labels.insert("app".to_string(), "postgres-cluster".to_string()); + labels.insert("type".to_string(), "database".to_string()); + labels.insert("engine".to_string(), "postgresql".to_string()); + + // Deploy the PostgreSQL cluster using the convenience method + println!("Deploying PostgreSQL cluster..."); + km.deploy_application( + "postgres-cluster", // name + "postgres:15", // image + 2, // replicas (1 master + 1 replica) + 5432, // port + Some(labels), // labels + ) + .await?; + + println!("āœ… PostgreSQL cluster deployed successfully!"); + + // Check deployment status + let deployments = km.deployments_list().await?; + let postgres_deployment = deployments + .iter() + .find(|d| d.metadata.name.as_ref() == Some(&"postgres-cluster".to_string())); + + if let Some(deployment) = postgres_deployment { + let total_replicas = deployment + .spec + .as_ref() + .and_then(|s| s.replicas) + .unwrap_or(0); + let ready_replicas = deployment + .status + .as_ref() + .and_then(|s| s.ready_replicas) + .unwrap_or(0); + + println!( + "Deployment status: {}/{} replicas ready", + ready_replicas, total_replicas + ); + } + + println!("\nšŸ“‹ Connection Information:"); + println!(" Host: postgres-cluster.database.svc.cluster.local"); + println!(" Port: 5432"); + println!(" Database: postgres (default)"); + println!(" Username: postgres (default)"); + println!(" Password: Set POSTGRES_PASSWORD environment variable"); + + println!("\nšŸ”§ To connect from another pod:"); + println!(" psql -h postgres-cluster.database.svc.cluster.local -U postgres"); + + println!("\nšŸ’” Next steps:"); + println!(" • Set environment variables for database credentials"); + println!(" • Add persistent volume claims for data storage"); + println!(" • Configure backup and monitoring"); + + Ok(()) +} diff --git a/examples/kubernetes/clusters/redis.rhai b/examples/kubernetes/clusters/redis.rhai new file mode 100644 index 0000000..939f87e --- /dev/null +++ b/examples/kubernetes/clusters/redis.rhai @@ -0,0 +1,44 @@ +//! Redis Cluster Deployment Example (Rhai) +//! +//! This script shows how to deploy a Redis cluster using Rhai scripting +//! with the KubernetesManager convenience methods. + +print("=== Redis Cluster Deployment ==="); + +// Create Kubernetes manager for the cache namespace +print("Creating Kubernetes manager for 'cache' namespace..."); +let km = kubernetes_manager_new("cache"); +print("āœ“ Kubernetes manager created"); + +// Create Redis cluster using the convenience method +print("\nDeploying Redis cluster..."); + +try { + // Deploy Redis using the convenience method + let result = deploy_application(km, "redis-cluster", "redis:7-alpine", 3, 6379, #{ + "app": "redis-cluster", + "type": "cache", + "engine": "redis" + }); + print("āœ“ " + result); + + print("\nāœ… Redis cluster deployed successfully!"); + + print("\nšŸ“‹ Connection Information:"); + print(" Host: redis-cluster.cache.svc.cluster.local"); + print(" Port: 6379"); + + print("\nšŸ”§ To connect from another pod:"); + print(" redis-cli -h redis-cluster.cache.svc.cluster.local"); + + print("\nšŸ’” Next steps:"); + print(" • Configure Redis authentication"); + print(" • Set up Redis clustering configuration"); + print(" • Add persistent storage"); + print(" • Configure memory policies"); + +} catch(e) { + print("āŒ Failed to deploy Redis cluster: " + e); +} + +print("\n=== Deployment Complete ==="); diff --git a/examples/kubernetes/clusters/redis.rs b/examples/kubernetes/clusters/redis.rs new file mode 100644 index 0000000..fb27832 --- /dev/null +++ b/examples/kubernetes/clusters/redis.rs @@ -0,0 +1,72 @@ +//! Redis Cluster Deployment Example +//! +//! This example shows how to deploy a Redis cluster using the +//! KubernetesManager convenience methods. + +use sal_kubernetes::KubernetesManager; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create Kubernetes manager for the cache namespace + let km = KubernetesManager::new("cache").await?; + + // Configure Redis-specific labels + let mut labels = HashMap::new(); + labels.insert("app".to_string(), "redis-cluster".to_string()); + labels.insert("type".to_string(), "cache".to_string()); + labels.insert("engine".to_string(), "redis".to_string()); + + // Deploy the Redis cluster using the convenience method + println!("Deploying Redis cluster..."); + km.deploy_application( + "redis-cluster", // name + "redis:7-alpine", // image + 3, // replicas (Redis cluster nodes) + 6379, // port + Some(labels), // labels + ) + .await?; + + println!("āœ… Redis cluster deployed successfully!"); + + // Check deployment status + let deployments = km.deployments_list().await?; + let redis_deployment = deployments + .iter() + .find(|d| d.metadata.name.as_ref() == Some(&"redis-cluster".to_string())); + + if let Some(deployment) = redis_deployment { + let total_replicas = deployment + .spec + .as_ref() + .and_then(|s| s.replicas) + .unwrap_or(0); + let ready_replicas = deployment + .status + .as_ref() + .and_then(|s| s.ready_replicas) + .unwrap_or(0); + + println!( + "Deployment status: {}/{} replicas ready", + ready_replicas, total_replicas + ); + } + + println!("\nšŸ“‹ Connection Information:"); + println!(" Host: redis-cluster.cache.svc.cluster.local"); + println!(" Port: 6379"); + println!(" Password: Configure REDIS_PASSWORD environment variable"); + + println!("\nšŸ”§ To connect from another pod:"); + println!(" redis-cli -h redis-cluster.cache.svc.cluster.local"); + + println!("\nšŸ’” Next steps:"); + println!(" • Configure Redis authentication with environment variables"); + println!(" • Set up Redis clustering configuration"); + println!(" • Add persistent volume claims for data persistence"); + println!(" • Configure memory limits and eviction policies"); + + Ok(()) +} diff --git a/kubernetes/examples/generic.rs b/kubernetes/examples/generic.rs new file mode 100644 index 0000000..94b7c66 --- /dev/null +++ b/kubernetes/examples/generic.rs @@ -0,0 +1,97 @@ +//! Generic Application Deployment Example +//! +//! This example shows how to deploy any containerized application using the +//! KubernetesManager convenience methods. This works for any Docker image. + +use sal_kubernetes::KubernetesManager; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create Kubernetes manager + let km = KubernetesManager::new("default").await?; + + // Example 1: Simple web server deployment + println!("=== Example 1: Simple Nginx Web Server ==="); + + km.deploy_application("web-server", "nginx:latest", 2, 80, None) + .await?; + println!("āœ… Nginx web server deployed!"); + + // Example 2: Node.js application with labels + println!("\n=== Example 2: Node.js Application ==="); + + let mut node_labels = HashMap::new(); + node_labels.insert("app".to_string(), "node-app".to_string()); + node_labels.insert("tier".to_string(), "backend".to_string()); + node_labels.insert("environment".to_string(), "production".to_string()); + + km.deploy_application( + "node-app", // name + "node:18-alpine", // image + 3, // replicas - scale to 3 instances + 3000, // port + Some(node_labels), // labels + ) + .await?; + + println!("āœ… Node.js application deployed!"); + + // Example 3: Database deployment (any database) + println!("\n=== Example 3: MongoDB Database ==="); + + let mut mongo_labels = HashMap::new(); + mongo_labels.insert("app".to_string(), "mongodb".to_string()); + mongo_labels.insert("type".to_string(), "database".to_string()); + mongo_labels.insert("engine".to_string(), "mongodb".to_string()); + + km.deploy_application( + "mongodb", // name + "mongo:6.0", // image + 1, // replicas - single instance for simplicity + 27017, // port + Some(mongo_labels), // labels + ) + .await?; + + println!("āœ… MongoDB deployed!"); + + // Check status of all deployments + println!("\n=== Checking Deployment Status ==="); + + let deployments = km.deployments_list().await?; + + for deployment in &deployments { + if let Some(name) = &deployment.metadata.name { + let total_replicas = deployment + .spec + .as_ref() + .and_then(|s| s.replicas) + .unwrap_or(0); + let ready_replicas = deployment + .status + .as_ref() + .and_then(|s| s.ready_replicas) + .unwrap_or(0); + + println!( + "{}: {}/{} replicas ready", + name, ready_replicas, total_replicas + ); + } + } + + println!("\nšŸŽ‰ All deployments completed!"); + println!("\nšŸ’” Key Points:"); + println!(" • Any Docker image can be deployed using this simple interface"); + println!(" • Use labels to organize and identify your applications"); + println!( + " • The same method works for databases, web servers, APIs, and any containerized app" + ); + println!(" • For advanced configuration, use the individual KubernetesManager methods"); + println!( + " • Environment variables and resource limits can be added via direct Kubernetes API" + ); + + Ok(()) +} diff --git a/kubernetes/examples/postgres.rs b/kubernetes/examples/postgres.rs new file mode 100644 index 0000000..dbbd389 --- /dev/null +++ b/kubernetes/examples/postgres.rs @@ -0,0 +1,73 @@ +//! PostgreSQL Cluster Deployment Example +//! +//! This example shows how to deploy a PostgreSQL cluster using the +//! KubernetesManager convenience methods. + +use sal_kubernetes::KubernetesManager; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create Kubernetes manager for the database namespace + let km = KubernetesManager::new("database").await?; + + // Configure PostgreSQL-specific labels + let mut labels = HashMap::new(); + labels.insert("app".to_string(), "postgres-cluster".to_string()); + labels.insert("type".to_string(), "database".to_string()); + labels.insert("engine".to_string(), "postgresql".to_string()); + + // Deploy the PostgreSQL cluster using the convenience method + println!("Deploying PostgreSQL cluster..."); + km.deploy_application( + "postgres-cluster", // name + "postgres:15", // image + 2, // replicas (1 master + 1 replica) + 5432, // port + Some(labels), // labels + ) + .await?; + + println!("āœ… PostgreSQL cluster deployed successfully!"); + + // Check deployment status + let deployments = km.deployments_list().await?; + let postgres_deployment = deployments + .iter() + .find(|d| d.metadata.name.as_ref() == Some(&"postgres-cluster".to_string())); + + if let Some(deployment) = postgres_deployment { + let total_replicas = deployment + .spec + .as_ref() + .and_then(|s| s.replicas) + .unwrap_or(0); + let ready_replicas = deployment + .status + .as_ref() + .and_then(|s| s.ready_replicas) + .unwrap_or(0); + + println!( + "Deployment status: {}/{} replicas ready", + ready_replicas, total_replicas + ); + } + + println!("\nšŸ“‹ Connection Information:"); + println!(" Host: postgres-cluster.database.svc.cluster.local"); + println!(" Port: 5432"); + println!(" Database: postgres (default)"); + println!(" Username: postgres (default)"); + println!(" Password: Set POSTGRES_PASSWORD environment variable"); + + println!("\nšŸ”§ To connect from another pod:"); + println!(" psql -h postgres-cluster.database.svc.cluster.local -U postgres"); + + println!("\nšŸ’” Next steps:"); + println!(" • Set environment variables for database credentials"); + println!(" • Add persistent volume claims for data storage"); + println!(" • Configure backup and monitoring"); + + Ok(()) +} diff --git a/kubernetes/examples/redis.rs b/kubernetes/examples/redis.rs new file mode 100644 index 0000000..fb27832 --- /dev/null +++ b/kubernetes/examples/redis.rs @@ -0,0 +1,72 @@ +//! Redis Cluster Deployment Example +//! +//! This example shows how to deploy a Redis cluster using the +//! KubernetesManager convenience methods. + +use sal_kubernetes::KubernetesManager; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> Result<(), Box> { + // Create Kubernetes manager for the cache namespace + let km = KubernetesManager::new("cache").await?; + + // Configure Redis-specific labels + let mut labels = HashMap::new(); + labels.insert("app".to_string(), "redis-cluster".to_string()); + labels.insert("type".to_string(), "cache".to_string()); + labels.insert("engine".to_string(), "redis".to_string()); + + // Deploy the Redis cluster using the convenience method + println!("Deploying Redis cluster..."); + km.deploy_application( + "redis-cluster", // name + "redis:7-alpine", // image + 3, // replicas (Redis cluster nodes) + 6379, // port + Some(labels), // labels + ) + .await?; + + println!("āœ… Redis cluster deployed successfully!"); + + // Check deployment status + let deployments = km.deployments_list().await?; + let redis_deployment = deployments + .iter() + .find(|d| d.metadata.name.as_ref() == Some(&"redis-cluster".to_string())); + + if let Some(deployment) = redis_deployment { + let total_replicas = deployment + .spec + .as_ref() + .and_then(|s| s.replicas) + .unwrap_or(0); + let ready_replicas = deployment + .status + .as_ref() + .and_then(|s| s.ready_replicas) + .unwrap_or(0); + + println!( + "Deployment status: {}/{} replicas ready", + ready_replicas, total_replicas + ); + } + + println!("\nšŸ“‹ Connection Information:"); + println!(" Host: redis-cluster.cache.svc.cluster.local"); + println!(" Port: 6379"); + println!(" Password: Configure REDIS_PASSWORD environment variable"); + + println!("\nšŸ”§ To connect from another pod:"); + println!(" redis-cli -h redis-cluster.cache.svc.cluster.local"); + + println!("\nšŸ’” Next steps:"); + println!(" • Configure Redis authentication with environment variables"); + println!(" • Set up Redis clustering configuration"); + println!(" • Add persistent volume claims for data persistence"); + println!(" • Configure memory limits and eviction policies"); + + Ok(()) +} diff --git a/kubernetes/src/kubernetes_manager.rs b/kubernetes/src/kubernetes_manager.rs index 91e5fa5..c8732f0 100644 --- a/kubernetes/src/kubernetes_manager.rs +++ b/kubernetes/src/kubernetes_manager.rs @@ -1202,6 +1202,71 @@ impl KubernetesManager { log::info!("Deleted namespace '{}'", name); Ok(()) } + + /// Deploy a complete application with deployment and service + /// + /// This convenience method creates both a deployment and a service for an application, + /// making it easy to deploy complete containerized applications with a single call. + /// + /// # Arguments + /// + /// * `name` - The name for both deployment and service + /// * `image` - The container image to deploy + /// * `replicas` - Number of replicas to create + /// * `port` - The port the application listens on + /// * `labels` - Optional labels for the resources + /// + /// # Returns + /// + /// * `KubernetesResult<()>` - Success or an error + /// + /// # Example + /// + /// ```rust,no_run + /// use sal_kubernetes::KubernetesManager; + /// use std::collections::HashMap; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Box> { + /// let km = KubernetesManager::new("default").await?; + /// + /// let mut labels = HashMap::new(); + /// labels.insert("app".to_string(), "my-app".to_string()); + /// + /// km.deploy_application("my-app", "node:18", 3, 3000, Some(labels)).await?; + /// Ok(()) + /// } + /// ``` + pub async fn deploy_application( + &self, + name: &str, + image: &str, + replicas: i32, + port: i32, + labels: Option>, + ) -> KubernetesResult<()> { + log::info!("Deploying application '{}' with image '{}'", name, image); + + // Create deployment + self.deployment_create(name, image, replicas, labels.clone()) + .await?; + + // Create service selector - use app=name if no labels provided + let selector = if let Some(ref labels) = labels { + labels.clone() + } else { + let mut default_selector = HashMap::new(); + default_selector.insert("app".to_string(), name.to_string()); + default_selector + }; + + // Create service + self.service_create(name, selector, port, Some(port)) + .await?; + + log::info!("Successfully deployed application '{}'", name); + Ok(()) + } } /// Determine if a Kubernetes API error is retryable diff --git a/kubernetes/src/rhai.rs b/kubernetes/src/rhai.rs index c2251a0..3189793 100644 --- a/kubernetes/src/rhai.rs +++ b/kubernetes/src/rhai.rs @@ -409,6 +409,44 @@ fn resource_counts(km: &mut KubernetesManager) -> Result Ok(rhai_map) } +/// Deploy a complete application with deployment and service +/// +/// # Arguments +/// +/// * `km` - Mutable reference to KubernetesManager +/// * `name` - Name of the application +/// * `image` - Container image to use +/// * `replicas` - Number of replicas +/// * `port` - Port the application listens on +/// * `labels` - Optional labels as a Map +/// +/// # Returns +/// +/// * `Result>` - Success message or an error +fn deploy_application( + km: &mut KubernetesManager, + name: String, + image: String, + replicas: i64, + port: i64, + labels: Map, +) -> Result> { + let labels_map: Option> = if labels.is_empty() { + None + } else { + Some( + labels + .into_iter() + .map(|(k, v)| (k.to_string(), v.to_string())) + .collect(), + ) + }; + + execute_async(km.deploy_application(&name, &image, replicas as i32, port as i32, labels_map))?; + + Ok(format!("Successfully deployed application '{}'", name)) +} + /// Delete a specific pod by name /// /// # Arguments @@ -543,6 +581,9 @@ pub fn register_kubernetes_module(engine: &mut Engine) -> Result<(), Box Date: Thu, 10 Jul 2025 00:40:11 +0300 Subject: [PATCH 2/2] feat: Add Kubernetes examples and update dependencies - Add Kubernetes examples demonstrating deployment of various applications (PostgreSQL, Redis, generic). This improves the documentation and provides practical usage examples. - Add `tokio` dependency for async examples. This enables the use of asynchronous operations in the examples. - Add `once_cell` dependency for improved resource management in Kubernetes module. This allows efficient management of singletons and other resources. --- Cargo.toml | 19 +- examples/kubernetes/clusters/generic.rs | 63 ++- examples/kubernetes/clusters/postgres.rhai | 34 ++ examples/kubernetes/clusters/postgres.rs | 39 ++ examples/kubernetes/clusters/redis.rhai | 35 ++ examples/kubernetes/clusters/redis.rs | 37 ++ kubernetes/Cargo.toml | 1 + kubernetes/README.md | 258 +++++++++++- kubernetes/examples/generic.rs | 97 ----- kubernetes/examples/postgres.rs | 73 ---- kubernetes/examples/redis.rs | 72 ---- kubernetes/src/kubernetes_manager.rs | 186 +++++---- kubernetes/src/rhai.rs | 175 +++++++- kubernetes/tests/crud_operations_test.rs | 149 +++++-- kubernetes/tests/deployment_env_vars_test.rs | 384 ++++++++++++++++++ kubernetes/tests/edge_cases_test.rs | 293 +++++++++++++ kubernetes/tests/rhai/crud_operations.rhai | 2 +- kubernetes/tests/rhai/env_vars_test.rhai | 199 +++++++++ kubernetes/tests/rhai/new_functions_test.rhai | 51 +++ kubernetes/tests/rhai/pod_env_vars_test.rhai | 142 +++++++ kubernetes/tests/rhai/run_all_tests.rhai | 14 +- kubernetes/tests/rhai_tests.rs | 69 +++- .../kubernetes/01_namespace_operations.rhai | 2 - rhai_tests/kubernetes/02_pod_management.rhai | 2 - .../kubernetes/03_pcre_pattern_matching.rhai | 2 - rhai_tests/kubernetes/04_error_handling.rhai | 2 - .../kubernetes/05_production_safety.rhai | 2 - rhai_tests/kubernetes/run_all_tests.rhai | 2 - test_service_manager.rhai | 29 -- 29 files changed, 1951 insertions(+), 482 deletions(-) delete mode 100644 kubernetes/examples/generic.rs delete mode 100644 kubernetes/examples/postgres.rs delete mode 100644 kubernetes/examples/redis.rs create mode 100644 kubernetes/tests/deployment_env_vars_test.rs create mode 100644 kubernetes/tests/edge_cases_test.rs create mode 100644 kubernetes/tests/rhai/env_vars_test.rhai create mode 100644 kubernetes/tests/rhai/new_functions_test.rhai create mode 100644 kubernetes/tests/rhai/pod_env_vars_test.rhai delete mode 100644 test_service_manager.rhai diff --git a/Cargo.toml b/Cargo.toml index 3b3b3b0..be745ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -88,7 +88,8 @@ urlencoding = "2.1.3" tokio-test = "0.4.4" [dependencies] -thiserror = "2.0.12" # For error handling in the main Error enum +thiserror = "2.0.12" # For error handling in the main Error enum +tokio = { workspace = true } # For async examples # Optional dependencies - users can choose which modules to include sal-git = { path = "git", optional = true } @@ -146,3 +147,19 @@ all = [ "rhai", "service_manager", ] + +# Examples +[[example]] +name = "postgres_cluster" +path = "examples/kubernetes/clusters/postgres.rs" +required-features = ["kubernetes"] + +[[example]] +name = "redis_cluster" +path = "examples/kubernetes/clusters/redis.rs" +required-features = ["kubernetes"] + +[[example]] +name = "generic_cluster" +path = "examples/kubernetes/clusters/generic.rs" +required-features = ["kubernetes"] diff --git a/examples/kubernetes/clusters/generic.rs b/examples/kubernetes/clusters/generic.rs index 94b7c66..9bb341c 100644 --- a/examples/kubernetes/clusters/generic.rs +++ b/examples/kubernetes/clusters/generic.rs @@ -11,10 +11,26 @@ async fn main() -> Result<(), Box> { // Create Kubernetes manager let km = KubernetesManager::new("default").await?; - // Example 1: Simple web server deployment - println!("=== Example 1: Simple Nginx Web Server ==="); + // Clean up any existing resources first + println!("=== Cleaning up existing resources ==="); + let apps_to_clean = ["web-server", "node-app", "mongodb"]; - km.deploy_application("web-server", "nginx:latest", 2, 80, None) + for app in &apps_to_clean { + match km.deployment_delete(app).await { + Ok(_) => println!("āœ“ Deleted existing deployment: {}", app), + Err(_) => println!("āœ“ No existing deployment to delete: {}", app), + } + + match km.service_delete(app).await { + Ok(_) => println!("āœ“ Deleted existing service: {}", app), + Err(_) => println!("āœ“ No existing service to delete: {}", app), + } + } + + // Example 1: Simple web server deployment + println!("\n=== Example 1: Simple Nginx Web Server ==="); + + km.deploy_application("web-server", "nginx:latest", 2, 80, None, None) .await?; println!("āœ… Nginx web server deployed!"); @@ -26,12 +42,20 @@ async fn main() -> Result<(), Box> { node_labels.insert("tier".to_string(), "backend".to_string()); node_labels.insert("environment".to_string(), "production".to_string()); + // Configure Node.js environment variables + let mut node_env_vars = HashMap::new(); + node_env_vars.insert("NODE_ENV".to_string(), "production".to_string()); + node_env_vars.insert("PORT".to_string(), "3000".to_string()); + node_env_vars.insert("LOG_LEVEL".to_string(), "info".to_string()); + node_env_vars.insert("MAX_CONNECTIONS".to_string(), "1000".to_string()); + km.deploy_application( - "node-app", // name - "node:18-alpine", // image - 3, // replicas - scale to 3 instances - 3000, // port - Some(node_labels), // labels + "node-app", // name + "node:18-alpine", // image + 3, // replicas - scale to 3 instances + 3000, // port + Some(node_labels), // labels + Some(node_env_vars), // environment variables ) .await?; @@ -45,12 +69,25 @@ async fn main() -> Result<(), Box> { mongo_labels.insert("type".to_string(), "database".to_string()); mongo_labels.insert("engine".to_string(), "mongodb".to_string()); + // Configure MongoDB environment variables + let mut mongo_env_vars = HashMap::new(); + mongo_env_vars.insert( + "MONGO_INITDB_ROOT_USERNAME".to_string(), + "admin".to_string(), + ); + mongo_env_vars.insert( + "MONGO_INITDB_ROOT_PASSWORD".to_string(), + "mongopassword".to_string(), + ); + mongo_env_vars.insert("MONGO_INITDB_DATABASE".to_string(), "myapp".to_string()); + km.deploy_application( - "mongodb", // name - "mongo:6.0", // image - 1, // replicas - single instance for simplicity - 27017, // port - Some(mongo_labels), // labels + "mongodb", // name + "mongo:6.0", // image + 1, // replicas - single instance for simplicity + 27017, // port + Some(mongo_labels), // labels + Some(mongo_env_vars), // environment variables ) .await?; diff --git a/examples/kubernetes/clusters/postgres.rhai b/examples/kubernetes/clusters/postgres.rhai index 75cd9bf..6d302d4 100644 --- a/examples/kubernetes/clusters/postgres.rhai +++ b/examples/kubernetes/clusters/postgres.rhai @@ -10,6 +10,35 @@ print("Creating Kubernetes manager for 'database' namespace..."); let km = kubernetes_manager_new("database"); print("āœ“ Kubernetes manager created"); +// Create the namespace if it doesn't exist +print("Creating namespace 'database' if it doesn't exist..."); +try { + create_namespace(km, "database"); + print("āœ“ Namespace 'database' created"); +} catch(e) { + if e.to_string().contains("already exists") { + print("āœ“ Namespace 'database' already exists"); + } else { + print("āš ļø Warning: " + e); + } +} + +// Clean up any existing resources first +print("\nCleaning up any existing PostgreSQL resources..."); +try { + delete_deployment(km, "postgres-cluster"); + print("āœ“ Deleted existing deployment"); +} catch(e) { + print("āœ“ No existing deployment to delete"); +} + +try { + delete_service(km, "postgres-cluster"); + print("āœ“ Deleted existing service"); +} catch(e) { + print("āœ“ No existing service to delete"); +} + // Create PostgreSQL cluster using the convenience method print("\nDeploying PostgreSQL cluster..."); @@ -19,6 +48,11 @@ try { "app": "postgres-cluster", "type": "database", "engine": "postgresql" + }, #{ + "POSTGRES_DB": "myapp", + "POSTGRES_USER": "postgres", + "POSTGRES_PASSWORD": "secretpassword", + "PGDATA": "/var/lib/postgresql/data/pgdata" }); print("āœ“ " + result); diff --git a/examples/kubernetes/clusters/postgres.rs b/examples/kubernetes/clusters/postgres.rs index dbbd389..b495940 100644 --- a/examples/kubernetes/clusters/postgres.rs +++ b/examples/kubernetes/clusters/postgres.rs @@ -11,12 +11,50 @@ async fn main() -> Result<(), Box> { // Create Kubernetes manager for the database namespace let km = KubernetesManager::new("database").await?; + // Create the namespace if it doesn't exist + println!("Creating namespace 'database' if it doesn't exist..."); + match km.namespace_create("database").await { + Ok(_) => println!("āœ“ Namespace 'database' created"), + Err(e) => { + if e.to_string().contains("already exists") { + println!("āœ“ Namespace 'database' already exists"); + } else { + return Err(e.into()); + } + } + } + + // Clean up any existing resources first + println!("Cleaning up any existing PostgreSQL resources..."); + match km.deployment_delete("postgres-cluster").await { + Ok(_) => println!("āœ“ Deleted existing deployment"), + Err(_) => println!("āœ“ No existing deployment to delete"), + } + + match km.service_delete("postgres-cluster").await { + Ok(_) => println!("āœ“ Deleted existing service"), + Err(_) => println!("āœ“ No existing service to delete"), + } + // Configure PostgreSQL-specific labels let mut labels = HashMap::new(); labels.insert("app".to_string(), "postgres-cluster".to_string()); labels.insert("type".to_string(), "database".to_string()); labels.insert("engine".to_string(), "postgresql".to_string()); + // Configure PostgreSQL environment variables + let mut env_vars = HashMap::new(); + env_vars.insert("POSTGRES_DB".to_string(), "myapp".to_string()); + env_vars.insert("POSTGRES_USER".to_string(), "postgres".to_string()); + env_vars.insert( + "POSTGRES_PASSWORD".to_string(), + "secretpassword".to_string(), + ); + env_vars.insert( + "PGDATA".to_string(), + "/var/lib/postgresql/data/pgdata".to_string(), + ); + // Deploy the PostgreSQL cluster using the convenience method println!("Deploying PostgreSQL cluster..."); km.deploy_application( @@ -25,6 +63,7 @@ async fn main() -> Result<(), Box> { 2, // replicas (1 master + 1 replica) 5432, // port Some(labels), // labels + Some(env_vars), // environment variables ) .await?; diff --git a/examples/kubernetes/clusters/redis.rhai b/examples/kubernetes/clusters/redis.rhai index 939f87e..6e75f2d 100644 --- a/examples/kubernetes/clusters/redis.rhai +++ b/examples/kubernetes/clusters/redis.rhai @@ -10,6 +10,35 @@ print("Creating Kubernetes manager for 'cache' namespace..."); let km = kubernetes_manager_new("cache"); print("āœ“ Kubernetes manager created"); +// Create the namespace if it doesn't exist +print("Creating namespace 'cache' if it doesn't exist..."); +try { + create_namespace(km, "cache"); + print("āœ“ Namespace 'cache' created"); +} catch(e) { + if e.to_string().contains("already exists") { + print("āœ“ Namespace 'cache' already exists"); + } else { + print("āš ļø Warning: " + e); + } +} + +// Clean up any existing resources first +print("\nCleaning up any existing Redis resources..."); +try { + delete_deployment(km, "redis-cluster"); + print("āœ“ Deleted existing deployment"); +} catch(e) { + print("āœ“ No existing deployment to delete"); +} + +try { + delete_service(km, "redis-cluster"); + print("āœ“ Deleted existing service"); +} catch(e) { + print("āœ“ No existing service to delete"); +} + // Create Redis cluster using the convenience method print("\nDeploying Redis cluster..."); @@ -19,6 +48,12 @@ try { "app": "redis-cluster", "type": "cache", "engine": "redis" + }, #{ + "REDIS_PASSWORD": "redispassword", + "REDIS_PORT": "6379", + "REDIS_DATABASES": "16", + "REDIS_MAXMEMORY": "256mb", + "REDIS_MAXMEMORY_POLICY": "allkeys-lru" }); print("āœ“ " + result); diff --git a/examples/kubernetes/clusters/redis.rs b/examples/kubernetes/clusters/redis.rs index fb27832..16b9fb7 100644 --- a/examples/kubernetes/clusters/redis.rs +++ b/examples/kubernetes/clusters/redis.rs @@ -11,12 +11,48 @@ async fn main() -> Result<(), Box> { // Create Kubernetes manager for the cache namespace let km = KubernetesManager::new("cache").await?; + // Create the namespace if it doesn't exist + println!("Creating namespace 'cache' if it doesn't exist..."); + match km.namespace_create("cache").await { + Ok(_) => println!("āœ“ Namespace 'cache' created"), + Err(e) => { + if e.to_string().contains("already exists") { + println!("āœ“ Namespace 'cache' already exists"); + } else { + return Err(e.into()); + } + } + } + + // Clean up any existing resources first + println!("Cleaning up any existing Redis resources..."); + match km.deployment_delete("redis-cluster").await { + Ok(_) => println!("āœ“ Deleted existing deployment"), + Err(_) => println!("āœ“ No existing deployment to delete"), + } + + match km.service_delete("redis-cluster").await { + Ok(_) => println!("āœ“ Deleted existing service"), + Err(_) => println!("āœ“ No existing service to delete"), + } + // Configure Redis-specific labels let mut labels = HashMap::new(); labels.insert("app".to_string(), "redis-cluster".to_string()); labels.insert("type".to_string(), "cache".to_string()); labels.insert("engine".to_string(), "redis".to_string()); + // Configure Redis environment variables + let mut env_vars = HashMap::new(); + env_vars.insert("REDIS_PASSWORD".to_string(), "redispassword".to_string()); + env_vars.insert("REDIS_PORT".to_string(), "6379".to_string()); + env_vars.insert("REDIS_DATABASES".to_string(), "16".to_string()); + env_vars.insert("REDIS_MAXMEMORY".to_string(), "256mb".to_string()); + env_vars.insert( + "REDIS_MAXMEMORY_POLICY".to_string(), + "allkeys-lru".to_string(), + ); + // Deploy the Redis cluster using the convenience method println!("Deploying Redis cluster..."); km.deploy_application( @@ -25,6 +61,7 @@ async fn main() -> Result<(), Box> { 3, // replicas (Redis cluster nodes) 6379, // port Some(labels), // labels + Some(env_vars), // environment variables ) .await?; diff --git a/kubernetes/Cargo.toml b/kubernetes/Cargo.toml index e2ce593..b07c347 100644 --- a/kubernetes/Cargo.toml +++ b/kubernetes/Cargo.toml @@ -39,6 +39,7 @@ log = "0.4" # Rhai scripting support (optional) rhai = { version = "1.12.0", features = ["sync"], optional = true } +once_cell = "1.20.2" # UUID for resource identification uuid = { version = "1.16.0", features = ["v4"] } diff --git a/kubernetes/README.md b/kubernetes/README.md index 8a6c135..d78b704 100644 --- a/kubernetes/README.md +++ b/kubernetes/README.md @@ -35,15 +35,96 @@ This package provides a high-level interface for managing Kubernetes clusters us ## Features +- **Application Deployment**: Deploy complete applications with a single method call +- **Environment Variables & Labels**: Configure containers with environment variables and Kubernetes labels +- **Resource Lifecycle Management**: Automatic cleanup and replacement of existing resources - **Namespace-scoped Management**: Each `KubernetesManager` instance operates on a single namespace - **Pod Management**: List, create, and manage pods - **Pattern-based Deletion**: Delete resources using PCRE pattern matching - **Namespace Operations**: Create and manage namespaces (idempotent operations) - **Resource Management**: Support for pods, services, deployments, configmaps, secrets, and more -- **Rhai Integration**: Full scripting support through Rhai wrappers +- **Rhai Integration**: Full scripting support through Rhai wrappers with environment variables + +## Core Concepts + +### Labels vs Environment Variables + +Understanding the difference between labels and environment variables is crucial for effective Kubernetes deployments: + +#### **Labels** (Kubernetes Metadata) + +- **Purpose**: Organize, select, and manage Kubernetes resources +- **Scope**: Kubernetes cluster management and resource organization +- **Visibility**: Used by Kubernetes controllers, selectors, and monitoring systems +- **Examples**: `app=my-app`, `tier=backend`, `environment=production`, `version=v1.2.3` +- **Use Cases**: Resource grouping, service discovery, monitoring labels, deployment strategies + +#### **Environment Variables** (Container Configuration) + +- **Purpose**: Configure application runtime behavior and settings +- **Scope**: Inside container processes - available to your application code +- **Visibility**: Accessible via `process.env`, `os.environ`, etc. in your application +- **Examples**: `NODE_ENV=production`, `DATABASE_URL=postgres://...`, `API_KEY=secret` +- **Use Cases**: Database connections, API keys, feature flags, runtime configuration + +#### **Example: Complete Application Configuration** + +```rust +// Labels: For Kubernetes resource management +let mut labels = HashMap::new(); +labels.insert("app".to_string(), "web-api".to_string()); // Service discovery +labels.insert("tier".to_string(), "backend".to_string()); // Architecture layer +labels.insert("environment".to_string(), "production".to_string()); // Deployment stage +labels.insert("version".to_string(), "v2.1.0".to_string()); // Release version + +// Environment Variables: For application configuration +let mut env_vars = HashMap::new(); +env_vars.insert("NODE_ENV".to_string(), "production".to_string()); // Runtime mode +env_vars.insert("DATABASE_URL".to_string(), "postgres://db:5432/app".to_string()); // DB connection +env_vars.insert("REDIS_URL".to_string(), "redis://cache:6379".to_string()); // Cache connection +env_vars.insert("LOG_LEVEL".to_string(), "info".to_string()); // Logging config +``` ## Usage +### Application Deployment (Recommended) + +Deploy complete applications with labels and environment variables: + +```rust +use sal_kubernetes::KubernetesManager; +use std::collections::HashMap; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let km = KubernetesManager::new("default").await?; + + // Configure labels for Kubernetes resource organization + let mut labels = HashMap::new(); + labels.insert("app".to_string(), "my-app".to_string()); + labels.insert("tier".to_string(), "backend".to_string()); + + // Configure environment variables for the container + let mut env_vars = HashMap::new(); + env_vars.insert("NODE_ENV".to_string(), "production".to_string()); + env_vars.insert("DATABASE_URL".to_string(), "postgres://db:5432/myapp".to_string()); + env_vars.insert("API_KEY".to_string(), "secret-api-key".to_string()); + + // Deploy application with deployment + service + km.deploy_application( + "my-app", // name + "node:18-alpine", // image + 3, // replicas + 3000, // port + Some(labels), // Kubernetes labels + Some(env_vars), // container environment variables + ).await?; + + println!("āœ… Application deployed successfully!"); + Ok(()) +} +``` + ### Basic Operations ```rust @@ -53,17 +134,17 @@ use sal_kubernetes::KubernetesManager; async fn main() -> Result<(), Box> { // Create a manager for the "default" namespace let km = KubernetesManager::new("default").await?; - + // List all pods in the namespace let pods = km.pods_list().await?; println!("Found {} pods", pods.len()); - + // Create a namespace (no error if it already exists) km.namespace_create("my-namespace").await?; - + // Delete resources matching a pattern km.delete("test-.*").await?; - + Ok(()) } ``` @@ -74,14 +155,24 @@ async fn main() -> Result<(), Box> { // Create Kubernetes manager for namespace let km = kubernetes_manager_new("default"); -// List pods +// Deploy application with labels and environment variables +deploy_application(km, "my-app", "node:18-alpine", 3, 3000, #{ + "app": "my-app", + "tier": "backend", + "environment": "production" +}, #{ + "NODE_ENV": "production", + "DATABASE_URL": "postgres://db:5432/myapp", + "API_KEY": "secret-api-key" +}); + +print("āœ… Application deployed!"); + +// Basic operations let pods = pods_list(km); print("Found " + pods.len() + " pods"); -// Create namespace -namespace_create(km, "my-app"); - -// Delete test resources +namespace_create(km, "my-namespace"); delete(km, "test-.*"); ``` @@ -98,6 +189,7 @@ delete(km, "test-.*"); ### Kubernetes Authentication The package uses the standard Kubernetes configuration methods: + - In-cluster configuration (when running in a pod) - Kubeconfig file (`~/.kube/config` or `KUBECONFIG` environment variable) - Service account tokens @@ -144,6 +236,18 @@ The main interface for Kubernetes operations. Each instance is scoped to a singl - `KubernetesManager::new(namespace)` - Create a manager for the specified namespace +#### Application Deployment + +- `deploy_application(name, image, replicas, port, labels, env_vars)` - Deploy complete application with deployment and service +- `deployment_create(name, image, replicas, labels, env_vars)` - Create deployment with environment variables and labels + +#### Resource Creation + +- `pod_create(name, image, labels, env_vars)` - Create pod with environment variables and labels +- `service_create(name, selector, port, target_port)` - Create service with port mapping +- `configmap_create(name, data)` - Create configmap with data +- `secret_create(name, data, secret_type)` - Create secret with data and optional type + #### Resource Listing - `pods_list()` - List all pods in the namespace @@ -160,6 +264,8 @@ The main interface for Kubernetes operations. Each instance is scoped to a singl - `pod_delete(name)` - Delete a specific pod by name - `service_delete(name)` - Delete a specific service by name - `deployment_delete(name)` - Delete a specific deployment by name +- `configmap_delete(name)` - Delete a specific configmap by name +- `secret_delete(name)` - Delete a specific secret by name #### Pattern-based Operations @@ -180,32 +286,93 @@ The main interface for Kubernetes operations. Each instance is scoped to a singl When using the Rhai integration, the following functions are available: +**Manager Creation & Application Deployment:** + - `kubernetes_manager_new(namespace)` - Create a KubernetesManager +- `deploy_application(km, name, image, replicas, port, labels, env_vars)` - Deploy application with environment variables + +**Resource Listing:** + - `pods_list(km)` - List pods - `services_list(km)` - List services - `deployments_list(km)` - List deployments +- `configmaps_list(km)` - List configmaps +- `secrets_list(km)` - List secrets - `namespaces_list(km)` - List all namespaces -- `delete(km, pattern)` - Delete resources matching pattern -- `namespace_create(km, name)` - Create namespace -- `namespace_exists(km, name)` - Check namespace existence - `resource_counts(km)` - Get resource counts + +**Resource Operations:** + +- `delete(km, pattern)` - Delete resources matching pattern - `pod_delete(km, name)` - Delete specific pod - `service_delete(km, name)` - Delete specific service - `deployment_delete(km, name)` - Delete specific deployment +- `configmap_delete(km, name)` - Delete specific configmap +- `secret_delete(km, name)` - Delete specific secret + +**Namespace Functions:** + +- `namespace_create(km, name)` - Create namespace +- `namespace_exists(km, name)` - Check namespace existence +- `namespace_delete(km, name)` - Delete namespace - `namespace(km)` - Get manager's namespace ## Examples -The `examples/kubernetes/` directory contains comprehensive examples: +The `examples/kubernetes/clusters/` directory contains comprehensive examples: -- `basic_operations.rhai` - Basic listing and counting operations -- `namespace_management.rhai` - Creating and managing namespaces -- `pattern_deletion.rhai` - Using PCRE patterns for bulk deletion -- `multi_namespace_operations.rhai` - Working across multiple namespaces +### Rust Examples + +Run with: `cargo run --example --features kubernetes` + +- `postgres` - PostgreSQL database deployment with environment variables +- `redis` - Redis cache deployment with configuration +- `generic` - Multiple application deployments (nginx, node.js, mongodb) + +### Rhai Examples + +Run with: `./target/debug/herodo examples/kubernetes/clusters/