From a7192da60fc73457b8effa26087572b33a3d1f79 Mon Sep 17 00:00:00 2001 From: Joshua Tracey Date: Mon, 30 Mar 2026 16:07:28 +0100 Subject: [PATCH 1/4] feat: general improvements --- .github/workflows/rust.yml | 27 +++++++++++++ constitution.md | 31 +++++++++++++++ src/builder.rs | 32 +++++----------- src/cli.rs | 2 +- src/config.rs | 4 +- src/deployment/k8sm8/daemonsets.rs | 6 +-- src/deployment/k8sm8/deployments.rs | 6 +-- src/deployment/k8sm8/events.rs | 9 +++-- src/deployment/k8sm8/logs.rs | 16 +++++--- src/deployment/k8sm8/mod.rs | 12 +++--- src/deployment/mod.rs | 4 +- src/environment.rs | 59 +++++++++++++---------------- src/generate.rs | 9 ++++- src/interactive.rs | 8 ++-- src/lib.rs | 33 +++++++--------- src/main.rs | 28 ++++---------- src/plan.rs | 8 +++- src/roomservice/mod.rs | 43 +++++++++------------ src/roomservice/room.rs | 34 ++++++----------- src/templates/mod.rs | 37 ++++++++++-------- src/tui/app.rs | 17 ++++----- src/utils.rs | 2 +- 22 files changed, 223 insertions(+), 204 deletions(-) create mode 100644 .github/workflows/rust.yml create mode 100644 constitution.md diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..466fcad --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,27 @@ +name: Rust CI + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + - name: Run Check Formatting + run: cargo fmt --all -- --check + - name: Run Clippy + run: cargo clippy -- -D warnings + - name: Run Tests + run: cargo test --verbose \ No newline at end of file diff --git a/constitution.md b/constitution.md new file mode 100644 index 0000000..eb63440 --- /dev/null +++ b/constitution.md @@ -0,0 +1,31 @@ +# Sailr Constitution + +This document defines the governing principles, engineering guidelines, and contributor expectations for the Sailr project. It serves as the ultimate source of truth for architectural decisions and project culture. + +## Engineering Philosophy + +Our engineering approach is driven by the pursuit of uncompromising quality, where both high performance and rigorous safety are non-negotiable defaults. + +* **Performance and Safety by Default:** Core tooling, systems software, and backend logic must prioritize memory safety, fearless concurrency, and zero-cost abstractions. We do not compromise on security or efficiency. +* **Clarity and Conciseness:** Code and documentation must be detailed and comprehensive when dealing with complex concepts, but strictly concise everywhere else. Avoid unnecessary verbosity, boilerplate, or repetition. +* **Idiomatic and Predictable Design:** Follow community-standard linting, enforce strict type checking, and use conventional commit structures. Maintainability is fundamental to our success. + +## Technology Stack Guidelines + +We practice pragmatic technology selection. We use the exact right tool for the specific domain, maintaining strict boundaries between different components of our architecture. + +* **Systems, Tooling, and Heavy Backend:** Prioritize uncompromising safety and performance architectures. + * *Primary Technology:* **Rust** +* **Lightweight Backend Services:** Emphasize simple, highly concurrent architectures for services where the maximum control of a systems language is overkill. + * *Primary Technology:* **Go** +* **Frontend:** Maintain strict isolation from backend logic, favoring fine-grained reactivity and minimal runtime overhead. + * *Primary Technology:* **SolidJS / TypeScript** +* **Machine Learning:** Default to standard Python-based ecosystems out of necessity, but actively seek out and transition to safer, compiled alternatives whenever viable. + +## Contributor Expectations + +Our collaboration model is built on directness, factual accuracy, and a shared commitment to excellence. + +* **Direct and Candid Collaboration:** Code reviews and discussions must be factual, straightforward, and grounded in reality. +* **Constructive Correction:** Correct misconceptions gently but firmly. Our highest priority is maintaining the project's engineering standards. +* **Focus on the Code:** Keep discussions centered on technical merit, architecture, and alignment with this constitution. diff --git a/src/builder.rs b/src/builder.rs index 2c50c1b..8cb9a78 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -31,12 +31,12 @@ impl Builder { let cache_dir = path_buf.to_str().unwrap().to_owned().to_string(); let cfg = env.build.clone().unwrap_fail("No config found."); - if cfg.before_all.is_some() { - self.roomservice.add_before_all(&cfg.before_all.unwrap()) + if let Some(before_all) = cfg.before_all { + self.roomservice.add_before_all(&before_all) } - if cfg.after_all.is_some() { - self.roomservice.add_after_all(&cfg.after_all.unwrap()) + if let Some(after_all) = cfg.after_all { + self.roomservice.add_after_all(&after_all) } //check_room_provided_to_flag("only".to_string(), &self.only, &cfg.rooms); @@ -47,21 +47,13 @@ impl Builder { let mut should_add = true; // @Note Check to see if it's in the only array - if self.only.len() > 0 { - if self.only.contains(&name) { - should_add = true - } else { - should_add = false - } + if !self.only.is_empty() { + should_add = self.only.contains(&name); } // @Note Check to see if it's in the ignore array - if self.ignore.len() > 0 { - if self.ignore.contains(&name) { - should_add = false - } else { - should_add = true - } + if !self.ignore.is_empty() { + should_add = !self.ignore.contains(&name); } if should_add { @@ -87,13 +79,9 @@ impl Builder { } } -pub fn split_matches<'a>(val: Option) -> Vec { +pub fn split_matches(val: Option) -> Vec { match val { - Some(ignore_values) => ignore_values - .split(',') - .into_iter() - .map(|t| t.to_string()) - .collect(), + Some(ignore_values) => ignore_values.split(',').map(|t| t.to_string()).collect(), None => vec![], } diff --git a/src/cli.rs b/src/cli.rs index 1f21a48..7180a76 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,6 +1,6 @@ use std::io; -use clap::{clap_derive::Args, command, Command, Parser, Subcommand, ValueEnum}; +use clap::{clap_derive::Args, Command, Parser, Subcommand, ValueEnum}; use clap_complete::{generate, Generator, Shell}; #[derive(Debug, Parser)] diff --git a/src/config.rs b/src/config.rs index f902586..40640ee 100644 --- a/src/config.rs +++ b/src/config.rs @@ -14,11 +14,11 @@ pub struct Config { impl Config { pub fn new( name: &String, - config_filenames: &Vec, + config_filenames: &[String], content: &String, dir: &String, ) -> Config { - let config_path = Path::new(&dir).join(&name); + let config_path = Path::new(&dir).join(name); Config { name: name.to_string(), diff --git a/src/deployment/k8sm8/daemonsets.rs b/src/deployment/k8sm8/daemonsets.rs index 0bfcf34..13289c3 100644 --- a/src/deployment/k8sm8/daemonsets.rs +++ b/src/deployment/k8sm8/daemonsets.rs @@ -1,8 +1,8 @@ use anyhow::Result; +use chrono; use k8s_openapi::api::apps::v1::DaemonSet; use kube::{Api, Client}; -use chrono; use crate::errors::KubeError; @@ -88,9 +88,7 @@ pub async fn restart_daemonset( daemonsets .patch(name, &kube::api::PatchParams::default(), &patch) .await - .map_err(|e| { - KubeError::ResourceUpdateFailed(format!("Failed to patch resource: {}", e)) - })?; + .map_err(|e| KubeError::ResourceUpdateFailed(format!("Failed to patch resource: {}", e)))?; Ok(()) } diff --git a/src/deployment/k8sm8/deployments.rs b/src/deployment/k8sm8/deployments.rs index f4fc35f..5981706 100644 --- a/src/deployment/k8sm8/deployments.rs +++ b/src/deployment/k8sm8/deployments.rs @@ -1,7 +1,7 @@ use anyhow::Result; +use chrono; use k8s_openapi::{self, api::apps::v1::Deployment}; use kube::{Api, Client}; -use chrono; use crate::errors::KubeError; @@ -82,9 +82,7 @@ pub async fn restart_deployment( deployments .patch(name, &kube::api::PatchParams::default(), &patch) .await - .map_err(|e| { - KubeError::ResourceUpdateFailed(format!("Failed to patch resource: {}", e)) - })?; + .map_err(|e| KubeError::ResourceUpdateFailed(format!("Failed to patch resource: {}", e)))?; Ok(()) } diff --git a/src/deployment/k8sm8/events.rs b/src/deployment/k8sm8/events.rs index d3d512c..2a4f2fc 100644 --- a/src/deployment/k8sm8/events.rs +++ b/src/deployment/k8sm8/events.rs @@ -24,16 +24,19 @@ pub async fn get_all_events(client: Client, namespace: &str) -> Result newest) so newest are at the bottom items.sort_by(|a, b| { let get_time = |event: &Event| { - event.last_timestamp.as_ref().map(|t| t.0) + event + .last_timestamp + .as_ref() + .map(|t| t.0) .or_else(|| event.event_time.as_ref().map(|t| t.0)) .or_else(|| event.metadata.creation_timestamp.as_ref().map(|t| t.0)) }; - + get_time(a).cmp(&get_time(b)) }); diff --git a/src/deployment/k8sm8/logs.rs b/src/deployment/k8sm8/logs.rs index aa41918..29bde30 100644 --- a/src/deployment/k8sm8/logs.rs +++ b/src/deployment/k8sm8/logs.rs @@ -13,7 +13,7 @@ struct ProcessedLog { fn process_log_line(tagged_log: TaggedLog) -> ProcessedLog { let (pod_name, log_line) = tagged_log; - let content = format!("{}", log_line); + let content = log_line.to_string(); ProcessedLog { pod_name, content } } @@ -202,7 +202,11 @@ pub async fn log_streamer( let log_params = LogParams { follow: true, timestamps: true, - since_seconds: if restart_count == 0 { Some(120) } else { Some(1) }, + since_seconds: if restart_count == 0 { + Some(120) + } else { + Some(1) + }, container: Some(container_name.clone()), ..Default::default() }; @@ -216,7 +220,7 @@ pub async fn log_streamer( match line_res { Ok(line) => { let processed = process_log_line((tag.clone(), line)); - if let Err(_) = tx.send(processed).await { + if tx.send(processed).await.is_err() { // Receiver dropped, stop everything return; } @@ -225,13 +229,13 @@ pub async fn log_streamer( } } } - Err(e) => { + Err(_e) => { // Only print connection error if it's the first attempt or intermittent // to avoid spamming on hard failures (though spam is info here) // eprintln!("Connection failed for {}: {}", pod_name, e); } } - + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; restart_count += 1; } @@ -257,6 +261,6 @@ pub async fn log_streamer( for handle in handles { handle.abort(); } - + Ok(()) } diff --git a/src/deployment/k8sm8/mod.rs b/src/deployment/k8sm8/mod.rs index 5b83400..01421c5 100644 --- a/src/deployment/k8sm8/mod.rs +++ b/src/deployment/k8sm8/mod.rs @@ -50,7 +50,6 @@ pub async fn create_client(context: String) -> Result { config.read_timeout = Some(Duration::from_secs(30)); config.write_timeout = Some(Duration::from_secs(30)); - let client = Client::try_from(config) .map_err(|e| KubeError::UnexpectedError(format!("Failed to create client: {}", e)))?; @@ -71,7 +70,11 @@ pub async fn get_cluster_resources( let discovery = match tokio::time::timeout(Duration::from_secs(10), discovery.run()).await { Ok(Ok(d)) => d, Ok(Err(e)) => return Err(anyhow::anyhow!("Failed to discover API resources: {}", e)), - Err(_) => return Err(anyhow::anyhow!("Failed to discover API resources: timed out")), + Err(_) => { + return Err(anyhow::anyhow!( + "Failed to discover API resources: timed out" + )) + } }; let gvk = if let Some(tm) = resource_type.types.clone() { @@ -145,8 +148,7 @@ pub async fn apply( .metadata .namespace .as_deref() - .or(Some("default")) - .unwrap() + .unwrap_or("default") .to_string(); name = obj.metadata.name.clone().unwrap_or_default(); let gvk = if let Some(tm) = &obj.types { @@ -161,7 +163,7 @@ pub async fn apply( "cannot apply object without valid TypeMeta {:?}", &obj )); - LOGGER.error(&format!("please add apiVersion and kind to the object")); + LOGGER.error("please add apiVersion and kind to the object"); continue; }; let name = &obj.metadata.name; diff --git a/src/deployment/mod.rs b/src/deployment/mod.rs index 2d7c331..71b6558 100644 --- a/src/deployment/mod.rs +++ b/src/deployment/mod.rs @@ -26,7 +26,7 @@ async fn apply_manifests_from_path( if file_path.is_file() && (file_path .extension() - .map_or(false, |ext| ext == "yaml" || ext == "yml")) + .is_some_and(|ext| ext == "yaml" || ext == "yml")) { LOGGER.debug(&format!("Applying manifest: {:?}", file_path)); let res = @@ -120,7 +120,7 @@ pub async fn deploy( if file_path.is_file() && (file_path .extension() - .map_or(false, |ext| ext == "yaml" || ext == "yml")) + .is_some_and(|ext| ext == "yaml" || ext == "yml")) { LOGGER.debug(&format!( "Processing file for pre-deletion: {:?}", diff --git a/src/environment.rs b/src/environment.rs index 0e779c8..00613fb 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -98,13 +98,10 @@ impl Environment { let env = toml::from_str::(&contents)?; // Use destructuring assignment if env.schema_version != "0.2.0" { - return Err(Box::new(std::io::Error::new( - std::io::ErrorKind::Other, - format!( - "Invalid schema version: expected {}, found {}", - "0.2.0", env.schema_version - ), - ))); + return Err(Box::new(std::io::Error::other(format!( + "Invalid schema version: expected {}, found {}", + "0.2.0", env.schema_version + )))); } Ok(env) @@ -115,34 +112,31 @@ impl Environment { let filemanager = filesystem::FileSystemManager::new( Path::new("./k8s/environments") - .join(self.name.to_string()) + .join(&self.name) .to_str() .unwrap() .to_string(), ); - filemanager.create_file(&format!("config.toml"), &contents)?; + filemanager.create_file(&"config.toml".to_string(), &contents)?; Ok(()) } pub fn get_variables(&self, service: &Service) -> Vec<(String, String)> { - let mut variables = Vec::new(); - variables.push(("name".to_string(), self.name.clone())); - variables.push(("log_level".to_string(), self.log_level.clone())); - variables.push(("domain".to_string(), self.domain.clone())); - - variables.push(("deployment_date".to_string(), get_current_timestamp())); - - variables.push(( - "default_replicas".to_string(), - self.default_replicas.to_string(), - )); - variables.push(("registry".to_string(), self.registry.clone())); - variables.push(("schema_version".to_string(), self.schema_version.clone())); - - variables.push(("service_name".to_string(), service.name.clone())); - - variables.push(("service_namespace".to_string(), service.namespace.clone())); + let mut variables = vec![ + ("name".to_string(), self.name.clone()), + ("log_level".to_string(), self.log_level.clone()), + ("domain".to_string(), self.domain.clone()), + ("deployment_date".to_string(), get_current_timestamp()), + ( + "default_replicas".to_string(), + self.default_replicas.to_string(), + ), + ("registry".to_string(), self.registry.clone()), + ("schema_version".to_string(), self.schema_version.clone()), + ("service_name".to_string(), service.name.clone()), + ("service_namespace".to_string(), service.namespace.clone()), + ]; if let Some(path) = &service.path { variables.push(("service_path".to_string(), path.clone())); @@ -198,6 +192,7 @@ pub struct Service { } impl Service { + #[allow(clippy::too_many_arguments)] pub fn new( name: &str, namespace: &str, @@ -225,12 +220,12 @@ impl Serialize for Service { fn serialize(&self, serializer: S) -> Result { let mut service = std::collections::HashMap::new(); service.insert("name".to_string(), self.name.to_string()); - if self.path.is_some() { - service.insert("path".to_string(), self.path.clone().unwrap()); + if let Some(path) = &self.path { + service.insert("path".to_string(), path.clone()); } - if self.build.is_some() { - service.insert("build".to_string(), self.build.clone().unwrap()); + if let Some(build) = &self.build { + service.insert("build".to_string(), build.clone()); } if self.tag.is_none() @@ -244,7 +239,7 @@ impl Serialize for Service { || self.patch_version.is_none() { service.insert("version".to_string(), self.tag.clone().unwrap()); - } else if self.tag.is_some() { + } else if let Some(tag) = &self.tag { service.insert( "version".to_string(), format!( @@ -252,7 +247,7 @@ impl Serialize for Service { self.major_version.unwrap(), self.minor_version.unwrap(), self.patch_version.unwrap(), - self.tag.as_ref().unwrap() + tag ), ); } else { diff --git a/src/generate.rs b/src/generate.rs index 6b59ba5..0cc7ca3 100644 --- a/src/generate.rs +++ b/src/generate.rs @@ -13,6 +13,12 @@ pub struct Generator { config_maps: Vec, } +impl Default for Generator { + fn default() -> Self { + Self::new() + } +} + impl Generator { pub fn new() -> Generator { Generator { @@ -23,7 +29,6 @@ impl Generator { } pub fn add_template(&mut self, original_template: &Template, new_content: String) { - if original_template.content.contains("ConfigMap") {} self.templates.push(Template::new( original_template.name.clone(), if original_template.content.contains("kind: ConfigMap") { @@ -50,7 +55,7 @@ impl Generator { for config_map in &self.config_maps { let path = Path::new(name) .join(&config_map.name) - .join(&"configMap.yaml".to_string()) + .join("configMap.yaml") .to_str() .unwrap() .to_string(); diff --git a/src/interactive.rs b/src/interactive.rs index b74c763..9609aca 100644 --- a/src/interactive.rs +++ b/src/interactive.rs @@ -163,11 +163,9 @@ async fn run_app(terminal: &mut crate::tui::Tui, app: &mut App) -> Result<(), Cl selected.push(item.clone()); } } - } else { - if let Some(idx) = app.selection_state.selected() { - if let Some(item) = items.get(idx) { - selected.push(item.clone()); - } + } else if let Some(idx) = app.selection_state.selected() { + if let Some(item) = items.get(idx) { + selected.push(item.clone()); } } diff --git a/src/lib.rs b/src/lib.rs index 7c43337..6f31ff5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -28,7 +28,7 @@ pub mod templates; pub mod tui; pub mod utils; -pub static LOGGER: Lazy> = Lazy::new(|| Logger::default()); +pub static LOGGER: Lazy> = Lazy::new(Logger::default); #[derive(Debug, Deserialize)] pub struct GlobalVars { @@ -77,7 +77,7 @@ pub fn load_global_vars() -> Result, Box) { let mut template_manager = TemplateManager::new(); - let (templates, config_maps) = match template_manager.read_templates(Some(&env)) { + let (templates, config_maps) = match template_manager.read_templates(Some(env)) { Ok((templates, config_maps)) => (templates, config_maps), Err(e) => { println!("Error: {:?}", e); @@ -96,10 +96,10 @@ pub fn generate(name: &str, env: &Environment, services: Vec<&Service>) { continue; } let content = template_manager - .replace_variables(template, &variables) + .replace_variables(template, variables) .unwrap(); - generator.add_template(&template, content) + generator.add_template(template, content) } for config in &config_maps { if config.name.split("/").last().unwrap() != service.name { @@ -110,12 +110,8 @@ pub fn generate(name: &str, env: &Environment, services: Vec<&Service>) { } } let res = generator.generate(&name.to_string()); - match res { - Ok(_) => (), - Err(e) => { - println!(": {:?}", e); - return; - } + if let Err(e) = res { + println!(": {:?}", e); } } @@ -126,15 +122,15 @@ pub fn create_default_env_config( ) { let mut vars = load_global_vars().unwrap(); - if vars.len() == 0 { + if vars.is_empty() { vars.insert("default_registry".to_string(), "docker.io".to_string()); vars.insert("default_domain".to_string(), "example.com".to_string()); } vars.insert("name".to_string(), name.clone()); - if registry.is_some() { - vars.insert("default_registry".to_string(), registry.unwrap()); + if let Some(r) = registry { + vars.insert("default_registry".to_string(), r); } let file_manager = FileSystemManager::new("./k8s/environments".to_string()); @@ -160,7 +156,6 @@ pub fn create_default_env_config( &generated_config, ) .unwrap(); - return; } else if let Some(config_template) = config_template { let content = file_manager .read_file(&config_template.clone(), Some(&"".to_string())) @@ -178,7 +173,6 @@ pub fn create_default_env_config( &generated_config, ) .unwrap(); - return; } else { let default_env_config = ( "config.toml".to_string(), @@ -206,22 +200,21 @@ pub fn create_default_env_infra( ) { let mut vars = load_global_vars().unwrap(); - if vars.len() == 0 { + if vars.is_empty() { vars.insert("default_registry".to_string(), "docker.io".to_string()); vars.insert("default_domain".to_string(), "example.com".to_string()); } vars.insert("name".to_string(), name.clone()); - if registry.is_some() { - println!("Registry: {:?}", registry); - vars.insert("default_registry".to_string(), registry.unwrap()); + if let Some(r) = registry { + println!("Registry: {:?}", Some(&r)); + vars.insert("default_registry".to_string(), r); } println!("Vars: {:?}", vars); if let Some(config_template) = infra_template { Infra::use_template(&name, &config_template, &mut vars); - return; } } diff --git a/src/main.rs b/src/main.rs index d30d1e1..49270c7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -273,7 +273,7 @@ async fn main() -> Result<(), CliError> { } Commands::Infra(a) => match a { InfraCommands::Up(arg) => { - LOGGER.info(&format!("Creating a new environment")); + LOGGER.info("Creating a new environment"); if let Some(template_path) = arg.infra_template_path { create_default_env_infra(arg.name, Some(template_path), arg.default_registry); } else if let Some(provider) = arg.provider { @@ -321,7 +321,7 @@ async fn main() -> Result<(), CliError> { } } Commands::Generate(arg) => { - LOGGER.info(&format!("Generating an environment")); + LOGGER.info("Generating an environment"); let env = match Environment::load_from_file(&arg.name) { Ok(env) => env, @@ -334,22 +334,16 @@ async fn main() -> Result<(), CliError> { let mut services = env.list_services(); if let Some(only_services) = arg.only { - services = services - .into_iter() - .filter(|s| only_services.contains(&s.name)) - .collect(); + services.retain(|s| only_services.contains(&s.name)); } if let Some(ignored_services) = arg.ignore { - services = services - .into_iter() - .filter(|s| !ignored_services.contains(&s.name)) - .collect(); + services.retain(|s| !ignored_services.contains(&s.name)); } generate(&arg.name, &env, services); - LOGGER.info(&format!("Generation Complete")); + LOGGER.info("Generation Complete"); } Commands::Build(arg) => { let env = match Environment::load_from_file(&arg.name) { @@ -397,17 +391,11 @@ async fn main() -> Result<(), CliError> { let mut services = env.list_services(); if let Some(ref ignored_services) = arg.ignore { - services = services - .into_iter() - .filter(|s| !ignored_services.contains(&s.name)) - .collect(); + services.retain(|s| !ignored_services.contains(&s.name)); } if let Some(ref only_services) = arg.only { - services = services - .into_iter() - .filter(|s| only_services.contains(&s.name)) - .collect(); + services.retain(|s| only_services.contains(&s.name)); } if !arg.skip_build { @@ -480,7 +468,7 @@ async fn main() -> Result<(), CliError> { )); // Validate service type - let valid_types = vec!["web-app", "worker", "database-client", "api"]; + let valid_types = ["web-app", "worker", "database-client", "api"]; if !valid_types.contains(&args.app_type.as_str()) { LOGGER.warn(&format!( "Unknown service type '{}'. Using default template. Valid types: {}", diff --git a/src/plan.rs b/src/plan.rs index 70c0a02..29b778f 100644 --- a/src/plan.rs +++ b/src/plan.rs @@ -61,6 +61,12 @@ pub struct PlanSummary { pub no_change: usize, } +impl Default for DeploymentPlan { + fn default() -> Self { + Self::new() + } +} + impl DeploymentPlan { pub fn new() -> Self { Self { @@ -436,7 +442,7 @@ fn analyze_manifest_file( // Parse YAML content let docs: Vec = serde_yaml::Deserializer::from_str(&content) - .map(|doc| Value::deserialize(doc)) + .map(Value::deserialize) .collect::, _>>()?; for doc in docs { diff --git a/src/roomservice/mod.rs b/src/roomservice/mod.rs index 0489b85..313cd06 100644 --- a/src/roomservice/mod.rs +++ b/src/roomservice/mod.rs @@ -19,7 +19,7 @@ pub struct RoomserviceBuilder { } impl RoomserviceBuilder { - pub fn new<'a>(project: String, cache_dir: String, force: bool) -> RoomserviceBuilder { + pub fn new(project: String, cache_dir: String, force: bool) -> RoomserviceBuilder { match std::fs::create_dir(&cache_dir) { Ok(_) => (), Err(e) => match e.kind() { @@ -129,13 +129,9 @@ impl RoomserviceBuilder { return; } - if self.before_all.is_some() { + if let Some(before_all) = &self.before_all { log(scribe_rust::Color::Blue, "Executing Before All", ""); - match exec_cmd( - "./", - &self.before_all.as_ref().unwrap(), - &"Before All".to_string(), - ) { + match exec_cmd("./", before_all, "Before All") { Ok(_) => (), Err(_) => fail("Error in Before All hook, aborting roomservice run"), } @@ -182,14 +178,10 @@ impl RoomserviceBuilder { }); } - if self.after_all.is_some() { + if let Some(after_all) = &self.after_all { log(scribe_rust::Color::Blue, "Executing After All", ""); - match exec_cmd( - "./", - &self.after_all.as_ref().unwrap(), - &"After All".to_string(), - ) { + match exec_cmd("./", after_all, "After All") { Ok(_) => (), Err(_) => fail("Error in After All hook, aborting roomservice run"), } @@ -222,19 +214,15 @@ fn exec_room_cmd(room: &mut RoomBuilder, cmd: Option) { let cwd = room.path.to_owned(); let name = &room.name; if should_build && !is_errored { - match cmd { - Some(cmd) => { - log( - scribe_rust::Color::Yellow, - "Starting", - &format!("==> {}", name), - ); - match exec_cmd(&cwd, &cmd, name) { - Ok(_) => (), - Err(_) => room.set_errored(), - } + if let Some(cmd) = cmd { + log( + scribe_rust::Color::Yellow, + "Starting", + &format!("==> {}", name), + ); + if exec_cmd(&cwd, &cmd, name).is_err() { + room.set_errored(); } - None => (), } } } @@ -266,6 +254,9 @@ fn exec_cmd(cwd: &str, cmd: &str, name: &str) -> Result<(), ()> { Err(()) } }, - _ => Err(fail("Unexpected error in exec_cmd")), + _ => { + fail("Unexpected error in exec_cmd"); + Err(()) + } } } diff --git a/src/roomservice/room.rs b/src/roomservice/room.rs index 5b2aaa2..400c47a 100644 --- a/src/roomservice/room.rs +++ b/src/roomservice/room.rs @@ -56,19 +56,16 @@ impl RoomBuilder { for maybe_file in builder.build() { let file = maybe_file.unwrap(); - match file.file_type() { - Some(entry) => { - if entry.is_file() { - if dump_scope { - scope.push_str(file.path().to_str().unwrap()); - scope.push_str("\n"); - } - - hash.push_str(&hash_file(file.path(), BLAKE2S)); - hash.push_str("\n"); + if let Some(entry) = file.file_type() { + if entry.is_file() { + if dump_scope { + scope.push_str(file.path().to_str().unwrap()); + scope.push('\n'); } + + hash.push_str(&hash_file(file.path(), BLAKE2S)); + hash.push('\n'); } - None => (), } } @@ -82,13 +79,10 @@ impl RoomBuilder { fn prev_hash(&self) -> Option { let mut path = String::new(); path.push_str(&self.cache_dir); - path.push_str("/"); + path.push('/'); path.push_str(&self.name); - match fs::read_to_string(path) { - Ok(content) => Some(content), - Err(_) => None, - } + fs::read_to_string(path).ok() } pub fn set_errored(&mut self) { @@ -98,7 +92,7 @@ impl RoomBuilder { pub fn write_hash(&self) { let mut path = String::new(); path.push_str(&self.cache_dir); - path.push_str("/"); + path.push('/'); path.push_str(&self.name); let mut file = File::create(path).unwrap(); match file.write_all(self.latest_hash.as_ref().unwrap().as_bytes()) { @@ -115,11 +109,7 @@ impl RoomBuilder { } else { match prev { Some(old_hash) => { - if old_hash == curr { - self.should_build = false; - } else { - self.should_build = true; - } + self.should_build = old_hash != curr; } None => self.should_build = true, } diff --git a/src/templates/mod.rs b/src/templates/mod.rs index 2b7d6e8..e1f59b8 100644 --- a/src/templates/mod.rs +++ b/src/templates/mod.rs @@ -30,6 +30,12 @@ pub struct TemplateManager { templates: Vec<(String, String)>, } +impl Default for TemplateManager { + fn default() -> Self { + Self::new() + } +} + impl TemplateManager { // Creates a new TemplateManager instance. // The `./k8s/templates` directory is used to store Sailr's base templates. @@ -103,7 +109,7 @@ impl TemplateManager { //if path is specified in path, read the templates from the path instead and append to the template_dirs if let Some(env) = &env { for service in &env.service_whitelist { - if service.path == None || service.path.clone().unwrap() == "".to_string() { + if service.path.is_none() || service.path.clone().unwrap() == "" { template_dirs.insert(service.name.clone()); continue; } @@ -112,22 +118,21 @@ impl TemplateManager { .path .clone() .unwrap() - .split("/") - .into_iter() + .split('/') .fold(Path::new(&"".to_string()).to_owned(), |acc, x| acc.join(x)); let parent = path.parent().unwrap().to_str().unwrap().to_string(); let service_template_dir = self.filemanager.read_dir(&parent)?; - template_dirs.remove(&path.parent().unwrap().to_str().unwrap().to_string()); + template_dirs.remove(path.parent().unwrap().to_str().unwrap()); let templates = service_template_dir.into_iter().filter_map(|x| { - let full_path = Path::new(path.parent().unwrap()) - .join(&x) - .to_str() - .unwrap() - .to_string(); - + let full_path = Path::new(path.parent().unwrap()) + .join(&x) + .to_str() + .unwrap() + .to_string(); + if self.filemanager.is_dir(&full_path) { Some(full_path) } else { @@ -149,7 +154,7 @@ impl TemplateManager { if template_name.contains(x.name.as_str()) { return true; } - return false; + false }) { continue; } @@ -217,7 +222,7 @@ impl TemplateManager { let config_content = self .filemanager - .read_file(&config_file, Some(&"./k8s/templates".to_string())) + .read_file(config_file, Some(&"./k8s/templates".to_string())) .unwrap(); acc.0.push_str(&format!("\n {:?}: |", &config)); @@ -244,14 +249,14 @@ impl TemplateManager { let mut content = template.content.clone(); for (key, value) in variables { - content = content.replace(&format!("{{{{{}}}}}", key), &value); + content = content.replace(&format!("{{{{{}}}}}", key), value); } match self.validate_yaml(content.clone()) { Ok(_) => log( Color::Green, "Passed Check", - &format!("{}", template.full_path), + &template.full_path.to_string(), ), Err(e) => { log( @@ -269,7 +274,7 @@ impl TemplateManager { // This checks for correct formatting and structure but does not involve schema validation. // An error is returned if the YAML string is invalid. pub fn validate_yaml(&self, yaml: String) -> Result<(), Box> { - let _ = match serde_yaml::from_str::(&yaml) { + match serde_yaml::from_str::(&yaml) { Ok(_) => (), Err(e) => { let location = e.location().unwrap(); @@ -280,7 +285,7 @@ impl TemplateManager { ) .into()); } - }; + } Ok(()) } } diff --git a/src/tui/app.rs b/src/tui/app.rs index 43fc92a..c72f0a3 100644 --- a/src/tui/app.rs +++ b/src/tui/app.rs @@ -45,13 +45,13 @@ impl Action { } pub fn is_multi_select(&self) -> bool { - match self { + !matches!( + self, Action::DisplayEvents - | Action::DescribePod - | Action::ShellIntoPod - | Action::PortForward => false, - _ => true, - } + | Action::DescribePod + | Action::ShellIntoPod + | Action::PortForward + ) } pub fn requires_input(&self) -> Option<&'static str> { @@ -63,10 +63,7 @@ impl Action { } pub fn skips_selection(&self) -> bool { - match self { - Action::DisplayEvents => true, - _ => false, - } + matches!(self, Action::DisplayEvents) } } diff --git a/src/utils.rs b/src/utils.rs index 97be413..c5e8a20 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -97,7 +97,7 @@ pub fn create_env_toml( pub fn get_env_toml(env_name: &str) -> Result { let env_name = env_name.to_lowercase().trim().replace(" ", "-"); - let env = std::fs::read_to_string(&format!("{}{}.toml", ENV_DIR, env_name))?; + let env = std::fs::read_to_string(format!("{}{}.toml", ENV_DIR, env_name))?; let toml: Environment = from_str(&env)?; Ok(toml) } From 24c34d146e4854788919b7e74d6d08a8c971f288 Mon Sep 17 00:00:00 2001 From: Joshua Tracey Date: Mon, 30 Mar 2026 19:57:06 +0100 Subject: [PATCH 2/4] fix: test now work --- completion/sailr.bash | 335 ++++++++++++++++++++++++----------- completion/sailr.zsh | 400 +++++++++++++++++++++++------------------- src/cli.rs | 89 +--------- src/environment.rs | 6 +- 4 files changed, 457 insertions(+), 373 deletions(-) diff --git a/completion/sailr.bash b/completion/sailr.bash index 33b1756..707c7c5 100644 --- a/completion/sailr.bash +++ b/completion/sailr.bash @@ -1,17 +1,24 @@ _sailr() { local i cur prev opts cmd COMPREPLY=() - cur="${COMP_WORDS[COMP_CWORD]}" - prev="${COMP_WORDS[COMP_CWORD-1]}" + if [[ "${BASH_VERSINFO[0]}" -ge 4 ]]; then + cur="$2" + else + cur="${COMP_WORDS[COMP_CWORD]}" + fi + prev="$3" cmd="" opts="" - for i in ${COMP_WORDS[@]} + for i in "${COMP_WORDS[@]:0:COMP_CWORD}" do case "${cmd},${i}" in ",$1") cmd="sailr" ;; + sailr,add-service) + cmd="sailr__add__service" + ;; sailr,build) cmd="sailr__build" ;; @@ -36,6 +43,12 @@ _sailr() { sailr,init) cmd="sailr__init" ;; + sailr,interactive) + cmd="sailr__interactive" + ;; + sailr__help,add-service) + cmd="sailr__help__add__service" + ;; sailr__help,build) cmd="sailr__help__build" ;; @@ -60,39 +73,33 @@ _sailr() { sailr__help,init) cmd="sailr__help__init" ;; - sailr__help__infra,apply) - cmd="sailr__help__infra__apply" - ;; - sailr__help__infra,create) - cmd="sailr__help__infra__create" + sailr__help,interactive) + cmd="sailr__help__interactive" ;; - sailr__help__infra,destroy) - cmd="sailr__help__infra__destroy" + sailr__help__infra,down) + cmd="sailr__help__infra__down" ;; - sailr__infra,apply) - cmd="sailr__infra__apply" + sailr__help__infra,up) + cmd="sailr__help__infra__up" ;; - sailr__infra,create) - cmd="sailr__infra__create" - ;; - sailr__infra,destroy) - cmd="sailr__infra__destroy" + sailr__infra,down) + cmd="sailr__infra__down" ;; sailr__infra,help) cmd="sailr__infra__help" ;; - sailr__infra__help,apply) - cmd="sailr__infra__help__apply" - ;; - sailr__infra__help,create) - cmd="sailr__infra__help__create" + sailr__infra,up) + cmd="sailr__infra__up" ;; - sailr__infra__help,destroy) - cmd="sailr__infra__help__destroy" + sailr__infra__help,down) + cmd="sailr__infra__help__down" ;; sailr__infra__help,help) cmd="sailr__infra__help__help" ;; + sailr__infra__help,up) + cmd="sailr__infra__help__up" + ;; *) ;; esac @@ -100,7 +107,7 @@ _sailr() { case "${cmd}" in sailr) - opts="-h -V --help --version init completions infra deploy generate build go help" + opts="-h -V --help --version init completions infra deploy generate build go add-service interactive help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -113,6 +120,52 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; + sailr__add__service) + opts="-t -p -i -n -h -V --type --port --image --name --help --version " + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --type) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -t) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --port) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --image) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -i) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --name) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -n) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; sailr__build) opts="-n -f -i -h -V --name --force --ignore --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then @@ -166,7 +219,7 @@ _sailr() { return 0 ;; sailr__deploy) - opts="-c -n -h -V --context --name --help --version" + opts="-c -n -N -h -V --context --name --namespace --strategy --apply --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -188,6 +241,18 @@ _sailr() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --namespace) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -N) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --strategy) + COMPREPLY=($(compgen -W "restart rolling" -- "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -196,7 +261,7 @@ _sailr() { return 0 ;; sailr__generate) - opts="-n -h -V --name --help --version" + opts="-n -o -i -h -V --name --only --ignore --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -210,6 +275,22 @@ _sailr() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --only) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -o) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --ignore) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -i) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -218,7 +299,7 @@ _sailr() { return 0 ;; sailr__go) - opts="-c -n -f -i -h -V --context --name --force --ignore --help --version" + opts="-c -n -N -s -f -i -o -h -V --context --name --namespace --skip-build --force --ignore --only --strategy --apply --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -240,12 +321,12 @@ _sailr() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - --force) - COMPREPLY=($(compgen -W "true false" -- "${cur}")) + --namespace) + COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - -f) - COMPREPLY=($(compgen -W "true false" -- "${cur}")) + -N) + COMPREPLY=($(compgen -f "${cur}")) return 0 ;; --ignore) @@ -256,6 +337,18 @@ _sailr() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --only) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -o) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --strategy) + COMPREPLY=($(compgen -W "restart rolling" -- "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -264,7 +357,7 @@ _sailr() { return 0 ;; sailr__help) - opts="init completions infra deploy generate build go help" + opts="init completions infra deploy generate build go add-service interactive help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -277,7 +370,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__build) + sailr__help__add__service) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -291,7 +384,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__completions) + sailr__help__build) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -305,7 +398,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__deploy) + sailr__help__completions) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -319,7 +412,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__generate) + sailr__help__deploy) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -333,7 +426,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__go) + sailr__help__generate) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -347,7 +440,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__help) + sailr__help__go) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -361,8 +454,8 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__infra) - opts="create apply destroy" + sailr__help__help) + opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -375,9 +468,9 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__infra__apply) - opts="" - if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + sailr__help__infra) + opts="up down" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi @@ -389,7 +482,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__infra__create) + sailr__help__infra__down) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -403,7 +496,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__help__infra__destroy) + sailr__help__infra__up) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -431,35 +524,13 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__infra) - opts="-h -V --help --version create apply destroy help" - if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - fi - case "${prev}" in - *) - COMPREPLY=() - ;; - esac - COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) - return 0 - ;; - sailr__infra__apply) - opts="-n -h -V --name --help --version" + sailr__help__interactive) + opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in - --name) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -n) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; *) COMPREPLY=() ;; @@ -467,37 +538,13 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__infra__create) - opts="-r -i -r -h -V --registry --infra-templates --region --help --version local aws gcp" - if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then + sailr__infra) + opts="-h -V --help --version up down help" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in - --registry) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -r) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - --infra-templates) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -i) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - --region) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; - -r) - COMPREPLY=($(compgen -f "${cur}")) - return 0 - ;; *) COMPREPLY=() ;; @@ -505,7 +552,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__infra__destroy) + sailr__infra__down) opts="-n -h -V --name --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -528,7 +575,7 @@ _sailr() { return 0 ;; sailr__infra__help) - opts="create apply destroy help" + opts="up down help" if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 @@ -541,7 +588,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__infra__help__apply) + sailr__infra__help__down) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -555,7 +602,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__infra__help__create) + sailr__infra__help__help) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -569,7 +616,7 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__infra__help__destroy) + sailr__infra__help__up) opts="" if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) @@ -583,13 +630,37 @@ _sailr() { COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 ;; - sailr__infra__help__help) - opts="" - if [[ ${cur} == -* || ${COMP_CWORD} -eq 4 ]] ; then + sailr__infra__up) + opts="-r -i -r -h -V --registry --infra-templates --region --help --version local aws gcp" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 3 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --registry) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -r) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --infra-templates) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -i) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --region) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -r) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; *) COMPREPLY=() ;; @@ -598,12 +669,20 @@ _sailr() { return 0 ;; sailr__init) - opts="-c -r -i -r -h -V --config-template --registry --infra-templates --region --help --version local aws gcp" + opts="-n -c -r -p -i -R -h -V --name --config-template --registry --provider --infra-templates --region --with-sample --no-sample --env-type --help --version" if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) return 0 fi case "${prev}" in + --name) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -n) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; --config-template) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -620,6 +699,14 @@ _sailr() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; + --provider) + COMPREPLY=($(compgen -W "local aws gcp" -- "${cur}")) + return 0 + ;; + -p) + COMPREPLY=($(compgen -W "local aws gcp" -- "${cur}")) + return 0 + ;; --infra-templates) COMPREPLY=($(compgen -f "${cur}")) return 0 @@ -632,7 +719,41 @@ _sailr() { COMPREPLY=($(compgen -f "${cur}")) return 0 ;; - -r) + -R) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --env-type) + COMPREPLY=($(compgen -W "development staging production" -- "${cur}")) + return 0 + ;; + *) + COMPREPLY=() + ;; + esac + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + ;; + sailr__interactive) + opts="-c -n -h -V --context --namespace --help --version" + if [[ ${cur} == -* || ${COMP_CWORD} -eq 2 ]] ; then + COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ) + return 0 + fi + case "${prev}" in + --context) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -c) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + --namespace) + COMPREPLY=($(compgen -f "${cur}")) + return 0 + ;; + -n) COMPREPLY=($(compgen -f "${cur}")) return 0 ;; diff --git a/completion/sailr.zsh b/completion/sailr.zsh index f47d717..71bfb07 100644 --- a/completion/sailr.zsh +++ b/completion/sailr.zsh @@ -14,7 +14,7 @@ _sailr() { fi local context curcontext="$curcontext" state line - _arguments "${_arguments_options[@]}" \ + _arguments "${_arguments_options[@]}" : \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -29,25 +29,30 @@ _sailr() { curcontext="${curcontext%:*:*}:sailr-command-$line[1]:" case $line[1] in (init) -_arguments "${_arguments_options[@]}" \ -'-c+[sailr config template path to use instead of the default one.]:Config Template Path: ' \ -'--config-template=[sailr config template path to use instead of the default one.]:Config Template Path: ' \ -'-r+[Default registry to use for images]:Default Registry: ' \ -'--registry=[Default registry to use for images]:Default Registry: ' \ -'-i+[Template path for infrastruture templates]:Infrastructure Template: ' \ -'--infra-templates=[Template path for infrastruture templates]:Infrastructure Template: ' \ -'-r+[Region to use for the provider]:Region: ' \ -'--region=[Region to use for the provider]:Region: ' \ +_arguments "${_arguments_options[@]}" : \ +'-n+[Name of the environment]:name:_default' \ +'--name=[Name of the environment]:name:_default' \ +'-c+[sailr config template path to use instead of the default one.]:Config Template Path:_default' \ +'--config-template=[sailr config template path to use instead of the default one.]:Config Template Path:_default' \ +'-r+[Default registry to use for images]:Default Registry:_default' \ +'--registry=[Default registry to use for images]:Default Registry:_default' \ +'-p+[Provider to use]:PROVIDER:(local aws gcp)' \ +'--provider=[Provider to use]:PROVIDER:(local aws gcp)' \ +'-i+[Template path for infrastruture templates]:Infrastructure Template:_default' \ +'--infra-templates=[Template path for infrastruture templates]:Infrastructure Template:_default' \ +'-R+[Region to use for the provider]:Region:_default' \ +'--region=[Region to use for the provider]:Region:_default' \ +'--env-type=[Environment type template to use]:ENV_TYPE:(development staging production)' \ +'--with-sample[Include a sample service for immediate testing (default\: true)]' \ +'(--with-sample)--no-sample[Skip creating sample service]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ '--version[Print version]' \ -':name -- Name of the environment:' \ -'::provider -- Provider to use:(local aws gcp)' \ && ret=0 ;; (completions) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -56,7 +61,7 @@ _arguments "${_arguments_options[@]}" \ && ret=0 ;; (infra) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -71,36 +76,26 @@ _arguments "${_arguments_options[@]}" \ (( CURRENT += 1 )) curcontext="${curcontext%:*:*}:sailr-infra-command-$line[1]:" case $line[1] in - (create) -_arguments "${_arguments_options[@]}" \ -'-r+[Default registry to use for images]:Default Registry: ' \ -'--registry=[Default registry to use for images]:Default Registry: ' \ -'-i+[Template path for infrastruture templates]:Infrastructure Template: ' \ -'--infra-templates=[Template path for infrastruture templates]:Infrastructure Template: ' \ -'-r+[Region to use for the provider]:Region: ' \ -'--region=[Region to use for the provider]:Region: ' \ + (up) +_arguments "${_arguments_options[@]}" : \ +'-r+[Default registry to use for images]:Default Registry:_default' \ +'--registry=[Default registry to use for images]:Default Registry:_default' \ +'-i+[Template path for infrastruture templates]:Infrastructure Template:_default' \ +'--infra-templates=[Template path for infrastruture templates]:Infrastructure Template:_default' \ +'-r+[Region to use for the provider]:Region:_default' \ +'--region=[Region to use for the provider]:Region:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ '--version[Print version]' \ -':name -- Name of the environment:' \ +':name -- Name of the environment:_default' \ '::provider -- Provider to use:(local aws gcp)' \ && ret=0 ;; -(apply) -_arguments "${_arguments_options[@]}" \ -'-n+[Name of the environment]:name: ' \ -'--name=[Name of the environment]:name: ' \ -'-h[Print help]' \ -'--help[Print help]' \ -'-V[Print version]' \ -'--version[Print version]' \ -&& ret=0 -;; -(destroy) -_arguments "${_arguments_options[@]}" \ -'-n+[Name of the environment]:name: ' \ -'--name=[Name of the environment]:name: ' \ +(down) +_arguments "${_arguments_options[@]}" : \ +'-n+[Name of the environment]:name:_default' \ +'--name=[Name of the environment]:name:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -108,7 +103,7 @@ _arguments "${_arguments_options[@]}" \ && ret=0 ;; (help) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ ":: :_sailr__infra__help_commands" \ "*::: :->help" \ && ret=0 @@ -119,20 +114,16 @@ _arguments "${_arguments_options[@]}" \ (( CURRENT += 1 )) curcontext="${curcontext%:*:*}:sailr-infra-help-command-$line[1]:" case $line[1] in - (create) -_arguments "${_arguments_options[@]}" \ -&& ret=0 -;; -(apply) -_arguments "${_arguments_options[@]}" \ + (up) +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; -(destroy) -_arguments "${_arguments_options[@]}" \ +(down) +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; (help) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; esac @@ -144,11 +135,15 @@ esac esac ;; (deploy) -_arguments "${_arguments_options[@]}" \ -'-c+[Kubernetes context to use]:context: ' \ -'--context=[Kubernetes context to use]:context: ' \ -'-n+[Name of the environment]:name: ' \ -'--name=[Name of the environment]:name: ' \ +_arguments "${_arguments_options[@]}" : \ +'-c+[Kubernetes context to use]:context:_default' \ +'--context=[Kubernetes context to use]:context:_default' \ +'-n+[Name of the environment]:name:_default' \ +'--name=[Name of the environment]:name:_default' \ +'-N+[Namespace to deploy to]:namespace:_default' \ +'--namespace=[Namespace to deploy to]:namespace:_default' \ +'--strategy=[Deployment strategy to use]:STRATEGY:(restart rolling)' \ +'--apply[Apply the deployment without planning first]' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -156,9 +151,13 @@ _arguments "${_arguments_options[@]}" \ && ret=0 ;; (generate) -_arguments "${_arguments_options[@]}" \ -'-n+[Name of the environment]:name: ' \ -'--name=[Name of the environment]:name: ' \ +_arguments "${_arguments_options[@]}" : \ +'-n+[Name of the environment]:name:_default' \ +'--name=[Name of the environment]:name:_default' \ +'-o+[]:ONLY:_default' \ +'--only=[]:ONLY:_default' \ +'-i+[]:IGNORE:_default' \ +'--ignore=[]:IGNORE:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -166,13 +165,13 @@ _arguments "${_arguments_options[@]}" \ && ret=0 ;; (build) -_arguments "${_arguments_options[@]}" \ -'-n+[Name of the environment]:name: ' \ -'--name=[Name of the environment]:name: ' \ +_arguments "${_arguments_options[@]}" : \ +'-n+[Name of the environment]:name:_default' \ +'--name=[Name of the environment]:name:_default' \ '-f+[Force all rooms to build, ignore the cache]:force:(true false)' \ '--force=[Force all rooms to build, ignore the cache]:force:(true false)' \ -'-i+[rooms to ignore from the build of the environment]:ignore: ' \ -'--ignore=[rooms to ignore from the build of the environment]:ignore: ' \ +'-i+[rooms to ignore from the build of the environment]:ignore:_default' \ +'--ignore=[rooms to ignore from the build of the environment]:ignore:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -180,15 +179,52 @@ _arguments "${_arguments_options[@]}" \ && ret=0 ;; (go) -_arguments "${_arguments_options[@]}" \ -'-c+[Kubernetes context to use]:context: ' \ -'--context=[Kubernetes context to use]:context: ' \ -'-n+[Name of the environment]:name: ' \ -'--name=[Name of the environment]:name: ' \ -'-f+[Force all rooms to build, ignore the cache]:force:(true false)' \ -'--force=[Force all rooms to build, ignore the cache]:force:(true false)' \ -'-i+[rooms to ignore from the build of the environment]:ignore: ' \ -'--ignore=[rooms to ignore from the build of the environment]:ignore: ' \ +_arguments "${_arguments_options[@]}" : \ +'-c+[Kubernetes context to use]:context:_default' \ +'--context=[Kubernetes context to use]:context:_default' \ +'-n+[Name of the environment]:name:_default' \ +'--name=[Name of the environment]:name:_default' \ +'-N+[Namespace to deploy to]:namespace:_default' \ +'--namespace=[Namespace to deploy to]:namespace:_default' \ +'-i+[rooms to ignore from the build of the environment]:ignore:_default' \ +'--ignore=[rooms to ignore from the build of the environment]:ignore:_default' \ +'-o+[]:ONLY:_default' \ +'--only=[]:ONLY:_default' \ +'--strategy=[Deployment strategy to use for the deploy step]:STRATEGY:(restart rolling)' \ +'-s[Skip the build step and run only generate and deploy steps]' \ +'--skip-build[Skip the build step and run only generate and deploy steps]' \ +'-f[Force all rooms to build, ignore the cache]' \ +'--force[Force all rooms to build, ignore the cache]' \ +'--apply[Apply the deployment without planning first]' \ +'-h[Print help]' \ +'--help[Print help]' \ +'-V[Print version]' \ +'--version[Print version]' \ +&& ret=0 +;; +(add-service) +_arguments "${_arguments_options[@]}" : \ +'-t+[Type of the application (e.g., web-app, worker)]:APP_TYPE:_default' \ +'--type=[Type of the application (e.g., web-app, worker)]:APP_TYPE:_default' \ +'-p+[Port for the service (default is 80)]:PORT:_default' \ +'--port=[Port for the service (default is 80)]:PORT:_default' \ +'-i+[Docker image for the service (default is '\''nginx\:latest'\'')]:IMAGE:_default' \ +'--image=[Docker image for the service (default is '\''nginx\:latest'\'')]:IMAGE:_default' \ +'-n+[Environment to add the service to]:ENV_NAME:_default' \ +'--name=[Environment to add the service to]:ENV_NAME:_default' \ +'-h[Print help]' \ +'--help[Print help]' \ +'-V[Print version]' \ +'--version[Print version]' \ +':service_name -- Name of the service:_default' \ +&& ret=0 +;; +(interactive) +_arguments "${_arguments_options[@]}" : \ +'-c+[Kubernetes context to use]:context:_default' \ +'--context=[Kubernetes context to use]:context:_default' \ +'-n+[Namespace to use]:namespace:_default' \ +'--namespace=[Namespace to use]:namespace:_default' \ '-h[Print help]' \ '--help[Print help]' \ '-V[Print version]' \ @@ -196,7 +232,7 @@ _arguments "${_arguments_options[@]}" \ && ret=0 ;; (help) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ ":: :_sailr__help_commands" \ "*::: :->help" \ && ret=0 @@ -208,15 +244,15 @@ _arguments "${_arguments_options[@]}" \ curcontext="${curcontext%:*:*}:sailr-help-command-$line[1]:" case $line[1] in (init) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; (completions) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; (infra) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ ":: :_sailr__help__infra_commands" \ "*::: :->infra" \ && ret=0 @@ -227,16 +263,12 @@ _arguments "${_arguments_options[@]}" \ (( CURRENT += 1 )) curcontext="${curcontext%:*:*}:sailr-help-infra-command-$line[1]:" case $line[1] in - (create) -_arguments "${_arguments_options[@]}" \ + (up) +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; -(apply) -_arguments "${_arguments_options[@]}" \ -&& ret=0 -;; -(destroy) -_arguments "${_arguments_options[@]}" \ +(down) +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; esac @@ -244,23 +276,31 @@ _arguments "${_arguments_options[@]}" \ esac ;; (deploy) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; (generate) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; (build) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; (go) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ +&& ret=0 +;; +(add-service) +_arguments "${_arguments_options[@]}" : \ +&& ret=0 +;; +(interactive) +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; (help) -_arguments "${_arguments_options[@]}" \ +_arguments "${_arguments_options[@]}" : \ && ret=0 ;; esac @@ -282,168 +322,174 @@ _sailr_commands() { 'generate:Generate an environment' \ 'build:Build related projects' \ 'go:Generate and deploy an environment' \ +'add-service:Add a new service to the project' \ +'interactive:Enter interactive terminal interface cli mode' \ 'help:Print this message or the help of the given subcommand(s)' \ ) _describe -t commands 'sailr commands' commands "$@" } -(( $+functions[_sailr__help__infra__apply_commands] )) || -_sailr__help__infra__apply_commands() { +(( $+functions[_sailr__add-service_commands] )) || +_sailr__add-service_commands() { local commands; commands=() - _describe -t commands 'sailr help infra apply commands' commands "$@" -} -(( $+functions[_sailr__infra__apply_commands] )) || -_sailr__infra__apply_commands() { - local commands; commands=() - _describe -t commands 'sailr infra apply commands' commands "$@" -} -(( $+functions[_sailr__infra__help__apply_commands] )) || -_sailr__infra__help__apply_commands() { - local commands; commands=() - _describe -t commands 'sailr infra help apply commands' commands "$@" + _describe -t commands 'sailr add-service commands' commands "$@" } (( $+functions[_sailr__build_commands] )) || _sailr__build_commands() { local commands; commands=() _describe -t commands 'sailr build commands' commands "$@" } -(( $+functions[_sailr__help__build_commands] )) || -_sailr__help__build_commands() { - local commands; commands=() - _describe -t commands 'sailr help build commands' commands "$@" -} (( $+functions[_sailr__completions_commands] )) || _sailr__completions_commands() { local commands; commands=() _describe -t commands 'sailr completions commands' commands "$@" } -(( $+functions[_sailr__help__completions_commands] )) || -_sailr__help__completions_commands() { +(( $+functions[_sailr__deploy_commands] )) || +_sailr__deploy_commands() { local commands; commands=() - _describe -t commands 'sailr help completions commands' commands "$@" + _describe -t commands 'sailr deploy commands' commands "$@" } -(( $+functions[_sailr__help__infra__create_commands] )) || -_sailr__help__infra__create_commands() { +(( $+functions[_sailr__generate_commands] )) || +_sailr__generate_commands() { local commands; commands=() - _describe -t commands 'sailr help infra create commands' commands "$@" + _describe -t commands 'sailr generate commands' commands "$@" } -(( $+functions[_sailr__infra__create_commands] )) || -_sailr__infra__create_commands() { +(( $+functions[_sailr__go_commands] )) || +_sailr__go_commands() { local commands; commands=() - _describe -t commands 'sailr infra create commands' commands "$@" + _describe -t commands 'sailr go commands' commands "$@" } -(( $+functions[_sailr__infra__help__create_commands] )) || -_sailr__infra__help__create_commands() { +(( $+functions[_sailr__help_commands] )) || +_sailr__help_commands() { + local commands; commands=( +'init:Initialize a new project' \ +'completions:Generate shell completions' \ +'infra:Manage environments' \ +'deploy:Deploy an environment' \ +'generate:Generate an environment' \ +'build:Build related projects' \ +'go:Generate and deploy an environment' \ +'add-service:Add a new service to the project' \ +'interactive:Enter interactive terminal interface cli mode' \ +'help:Print this message or the help of the given subcommand(s)' \ + ) + _describe -t commands 'sailr help commands' commands "$@" +} +(( $+functions[_sailr__help__add-service_commands] )) || +_sailr__help__add-service_commands() { local commands; commands=() - _describe -t commands 'sailr infra help create commands' commands "$@" + _describe -t commands 'sailr help add-service commands' commands "$@" } -(( $+functions[_sailr__deploy_commands] )) || -_sailr__deploy_commands() { +(( $+functions[_sailr__help__build_commands] )) || +_sailr__help__build_commands() { local commands; commands=() - _describe -t commands 'sailr deploy commands' commands "$@" + _describe -t commands 'sailr help build commands' commands "$@" +} +(( $+functions[_sailr__help__completions_commands] )) || +_sailr__help__completions_commands() { + local commands; commands=() + _describe -t commands 'sailr help completions commands' commands "$@" } (( $+functions[_sailr__help__deploy_commands] )) || _sailr__help__deploy_commands() { local commands; commands=() _describe -t commands 'sailr help deploy commands' commands "$@" } -(( $+functions[_sailr__help__infra__destroy_commands] )) || -_sailr__help__infra__destroy_commands() { +(( $+functions[_sailr__help__generate_commands] )) || +_sailr__help__generate_commands() { local commands; commands=() - _describe -t commands 'sailr help infra destroy commands' commands "$@" + _describe -t commands 'sailr help generate commands' commands "$@" } -(( $+functions[_sailr__infra__destroy_commands] )) || -_sailr__infra__destroy_commands() { +(( $+functions[_sailr__help__go_commands] )) || +_sailr__help__go_commands() { local commands; commands=() - _describe -t commands 'sailr infra destroy commands' commands "$@" + _describe -t commands 'sailr help go commands' commands "$@" } -(( $+functions[_sailr__infra__help__destroy_commands] )) || -_sailr__infra__help__destroy_commands() { +(( $+functions[_sailr__help__help_commands] )) || +_sailr__help__help_commands() { local commands; commands=() - _describe -t commands 'sailr infra help destroy commands' commands "$@" + _describe -t commands 'sailr help help commands' commands "$@" } -(( $+functions[_sailr__generate_commands] )) || -_sailr__generate_commands() { +(( $+functions[_sailr__help__infra_commands] )) || +_sailr__help__infra_commands() { + local commands; commands=( +'up:' \ +'down:' \ + ) + _describe -t commands 'sailr help infra commands' commands "$@" +} +(( $+functions[_sailr__help__infra__down_commands] )) || +_sailr__help__infra__down_commands() { local commands; commands=() - _describe -t commands 'sailr generate commands' commands "$@" + _describe -t commands 'sailr help infra down commands' commands "$@" } -(( $+functions[_sailr__help__generate_commands] )) || -_sailr__help__generate_commands() { +(( $+functions[_sailr__help__infra__up_commands] )) || +_sailr__help__infra__up_commands() { local commands; commands=() - _describe -t commands 'sailr help generate commands' commands "$@" + _describe -t commands 'sailr help infra up commands' commands "$@" } -(( $+functions[_sailr__go_commands] )) || -_sailr__go_commands() { +(( $+functions[_sailr__help__init_commands] )) || +_sailr__help__init_commands() { local commands; commands=() - _describe -t commands 'sailr go commands' commands "$@" + _describe -t commands 'sailr help init commands' commands "$@" } -(( $+functions[_sailr__help__go_commands] )) || -_sailr__help__go_commands() { +(( $+functions[_sailr__help__interactive_commands] )) || +_sailr__help__interactive_commands() { local commands; commands=() - _describe -t commands 'sailr help go commands' commands "$@" + _describe -t commands 'sailr help interactive commands' commands "$@" } -(( $+functions[_sailr__help_commands] )) || -_sailr__help_commands() { +(( $+functions[_sailr__infra_commands] )) || +_sailr__infra_commands() { local commands; commands=( -'init:Initialize a new project' \ -'completions:Generate shell completions' \ -'infra:Manage environments' \ -'deploy:Deploy an environment' \ -'generate:Generate an environment' \ -'build:Build related projects' \ -'go:Generate and deploy an environment' \ +'up:' \ +'down:' \ 'help:Print this message or the help of the given subcommand(s)' \ ) - _describe -t commands 'sailr help commands' commands "$@" + _describe -t commands 'sailr infra commands' commands "$@" } -(( $+functions[_sailr__help__help_commands] )) || -_sailr__help__help_commands() { +(( $+functions[_sailr__infra__down_commands] )) || +_sailr__infra__down_commands() { local commands; commands=() - _describe -t commands 'sailr help help commands' commands "$@" + _describe -t commands 'sailr infra down commands' commands "$@" } (( $+functions[_sailr__infra__help_commands] )) || _sailr__infra__help_commands() { local commands; commands=( -'create:' \ -'apply:' \ -'destroy:' \ +'up:' \ +'down:' \ 'help:Print this message or the help of the given subcommand(s)' \ ) _describe -t commands 'sailr infra help commands' commands "$@" } +(( $+functions[_sailr__infra__help__down_commands] )) || +_sailr__infra__help__down_commands() { + local commands; commands=() + _describe -t commands 'sailr infra help down commands' commands "$@" +} (( $+functions[_sailr__infra__help__help_commands] )) || _sailr__infra__help__help_commands() { local commands; commands=() _describe -t commands 'sailr infra help help commands' commands "$@" } -(( $+functions[_sailr__help__infra_commands] )) || -_sailr__help__infra_commands() { - local commands; commands=( -'create:' \ -'apply:' \ -'destroy:' \ - ) - _describe -t commands 'sailr help infra commands' commands "$@" -} -(( $+functions[_sailr__infra_commands] )) || -_sailr__infra_commands() { - local commands; commands=( -'create:' \ -'apply:' \ -'destroy:' \ -'help:Print this message or the help of the given subcommand(s)' \ - ) - _describe -t commands 'sailr infra commands' commands "$@" +(( $+functions[_sailr__infra__help__up_commands] )) || +_sailr__infra__help__up_commands() { + local commands; commands=() + _describe -t commands 'sailr infra help up commands' commands "$@" } -(( $+functions[_sailr__help__init_commands] )) || -_sailr__help__init_commands() { +(( $+functions[_sailr__infra__up_commands] )) || +_sailr__infra__up_commands() { local commands; commands=() - _describe -t commands 'sailr help init commands' commands "$@" + _describe -t commands 'sailr infra up commands' commands "$@" } (( $+functions[_sailr__init_commands] )) || _sailr__init_commands() { local commands; commands=() _describe -t commands 'sailr init commands' commands "$@" } +(( $+functions[_sailr__interactive_commands] )) || +_sailr__interactive_commands() { + local commands; commands=() + _describe -t commands 'sailr interactive commands' commands "$@" +} if [ "$funcstack[1]" = "_sailr" ]; then _sailr "$@" diff --git a/src/cli.rs b/src/cli.rs index 7180a76..c45d556 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -419,7 +419,7 @@ mod tests { "--name", "test-env", "--strategy", - "Restart", + "restart", ]) .unwrap(); match cli.commands { @@ -442,7 +442,7 @@ mod tests { "--name", "test-env", "--strategy", - "Rolling", + "rolling", ]) .unwrap(); match cli.commands { @@ -469,7 +469,7 @@ mod tests { .unwrap(); match cli.commands { Commands::Deploy(args) => { - assert_eq!(args.strategy, DeploymentStrategy::Restart); + assert_eq!(args.strategy, DeploymentStrategy::Rolling); assert_eq!(args.context, "test-context"); assert_eq!(args.name, "test-env"); } @@ -491,87 +491,4 @@ mod tests { ]); assert!(result.is_err()); } - - #[test] - fn test_go_args_strategy_restart() { - let cli = Cli::try_parse_from(&[ - "sailr", - "go", - "--context", - "test-context", - "--name", - "test-env", - "--strategy", - "Restart", - ]) - .unwrap(); - match cli.commands { - Commands::Go(args) => { - assert_eq!(args.strategy, DeploymentStrategy::Restart); - assert_eq!(args.context, "test-context"); - assert_eq!(args.name, "test-env"); - } - _ => panic!("Expected Go command"), - } - } - - #[test] - fn test_go_args_strategy_rolling() { - let cli = Cli::try_parse_from(&[ - "sailr", - "go", - "--context", - "test-context", - "--name", - "test-env", - "--strategy", - "Rolling", - ]) - .unwrap(); - match cli.commands { - Commands::Go(args) => { - assert_eq!(args.strategy, DeploymentStrategy::Rolling); - assert_eq!(args.context, "test-context"); - assert_eq!(args.name, "test-env"); - } - _ => panic!("Expected Go command"), - } - } - - #[test] - fn test_go_args_strategy_default() { - // Assumes DeploymentStrategy::Restart is the default - let cli = Cli::try_parse_from(&[ - "sailr", - "go", - "--context", - "test-context", - "--name", - "test-env", - ]) - .unwrap(); - match cli.commands { - Commands::Go(args) => { - assert_eq!(args.strategy, DeploymentStrategy::Restart); - assert_eq!(args.context, "test-context"); - assert_eq!(args.name, "test-env"); - } - _ => panic!("Expected Go command"), - } - } - - #[test] - fn test_go_args_strategy_invalid() { - let result = Cli::try_parse_from(&[ - "sailr", - "go", - "--context", - "test-context", - "--name", - "test-env", - "--strategy", - "InvalidStrategy", - ]); - assert!(result.is_err()); - } } diff --git a/src/environment.rs b/src/environment.rs index 00613fb..c877aa1 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -474,7 +474,7 @@ mod tests { assert_eq!( serialized, json!({ - "name": "my-namespace/my-service", + "name": "my-service", "path": "/api/v1", "build": "build-123", "version": "1.2.3-beta" @@ -499,7 +499,7 @@ mod tests { assert_eq!( serialized, json!({ - "name": "default/another-service", + "name": "another-service", "version": "0.1.0" }) ); @@ -541,7 +541,7 @@ mod tests { Service { name: "test-service".to_string(), namespace: "default".to_string(), - path: Some("".to_string()), + path: None, build: None, major_version: None, minor_version: None, From 21a75e3753af220bbdffae87cd1e901455367ee5 Mon Sep 17 00:00:00 2001 From: Joshua Tracey Date: Mon, 30 Mar 2026 20:10:53 +0100 Subject: [PATCH 3/4] feat: general improvements --- constitution.md | 7 ++ src/cli.rs | 2 +- src/environment.rs | 177 +++++++++++++++++++++++++++++++-------------- 3 files changed, 132 insertions(+), 54 deletions(-) diff --git a/constitution.md b/constitution.md index eb63440..ab049e2 100644 --- a/constitution.md +++ b/constitution.md @@ -29,3 +29,10 @@ Our collaboration model is built on directness, factual accuracy, and a shared c * **Direct and Candid Collaboration:** Code reviews and discussions must be factual, straightforward, and grounded in reality. * **Constructive Correction:** Correct misconceptions gently but firmly. Our highest priority is maintaining the project's engineering standards. * **Focus on the Code:** Keep discussions centered on technical merit, architecture, and alignment with this constitution. + +## Amendments + +### Amendment I: Uncompromising Error Handling and Data Integrity +* **Zero-Panic Policy:** Use of `.unwrap()` or `.expect()` is strictly forbidden in core domain logic, serialization, and deserialization routines. All fallible operations must propagate errors via `Result` or safely resolve to `None`. Panics are exclusively reserved for unrecoverable state corruption, never for unexpected input. +* **Symmetrical Data Structures:** Any data model that interacts with external inputs (e.g., config parsing) must be predictably and safely serializable and deserializable. Silent dropping of valid data or state during parsing is considered a critical defect. +* **Truth in Documentation:** Inline comments must strictly reflect the code's factual reality. Stale comments or misleading inline documentation that contradicts actual code behavior (especially within tests and domain rules) are worse than absent documentation and must be corrected as bugs. diff --git a/src/cli.rs b/src/cli.rs index c45d556..0b74f1e 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -457,7 +457,7 @@ mod tests { #[test] fn test_deploy_args_strategy_default() { - // Assumes DeploymentStrategy::Restart is the default + // Assumes DeploymentStrategy::Rolling is the default let cli = Cli::try_parse_from(&[ "sailr", "deploy", diff --git a/src/environment.rs b/src/environment.rs index c877aa1..1fa2872 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -234,32 +234,23 @@ impl Serialize for Service { && self.patch_version.is_none() { service.insert("version".to_string(), "latest".to_string()); - } else if self.major_version.is_none() - || self.minor_version.is_none() - || self.patch_version.is_none() - { - service.insert("version".to_string(), self.tag.clone().unwrap()); + } else if self.major_version.is_some() { + let mut version_str = self.major_version.unwrap().to_string(); + + if let Some(minor) = self.minor_version { + version_str.push_str(&format!(".{}", minor)); + } + if let Some(patch) = self.patch_version { + version_str.push_str(&format!(".{}", patch)); + } + if let Some(tag) = &self.tag { + version_str.push_str(&format!("-{}", tag)); + } + service.insert("version".to_string(), version_str); } else if let Some(tag) = &self.tag { - service.insert( - "version".to_string(), - format!( - "{}.{}.{}-{}", - self.major_version.unwrap(), - self.minor_version.unwrap(), - self.patch_version.unwrap(), - tag - ), - ); + service.insert("version".to_string(), tag.clone()); } else { - service.insert( - "version".to_string(), - format!( - "{}.{}.{}", - self.major_version.unwrap(), - self.minor_version.unwrap(), - self.patch_version.unwrap() - ), - ); + service.insert("version".to_string(), "latest".to_string()); } service.serialize(serializer) } @@ -289,44 +280,59 @@ impl<'de> Deserialize<'de> for Service { let mut major_version: Option = None; let mut minor_version: Option = None; let mut patch_version: Option = None; - let mut tag: Option = None; + let tag: Option; - // NEW LOGIC: - // If it doesn't contain a dot, it's a pure tag (e.g., "latest" or "2511-rc") - if !version.contains('.') { - tag = Some(version.to_string()); - } else { - // It contains a dot, so parse as semver (e.g., "1.2.3", "8.0", "1.2.3-beta") - let mut version_parts = version.split('.'); + let is_semver_like = version.chars().next().map_or(false, |c| c.is_ascii_digit()); + let mut valid_semver = true; - if let Some(major_str) = version_parts.next() { + if is_semver_like { + let (base, parsed_tag) = match version.split_once('-') { + Some((b, t)) => (b, Some(t.to_string())), + None => (version.as_str(), None), + }; + + let mut parts = base.split('.'); + + if let Some(major_str) = parts.next() { if let Ok(major) = major_str.parse::() { major_version = Some(major); - } - } - if let Some(minor_str) = version_parts.next() { - if let Ok(minor) = minor_str.parse::() { - minor_version = Some(minor); - } - } - - if let Some(patch_str) = version_parts.next() { - if let Ok(patch) = patch_str.parse::() { - patch_version = Some(patch); - } else if patch_str.contains('-') { - // This handles the "patch-tag" case (e.g., "3-beta") - let mut patch_tag_split = patch_str.split('-'); - if let Some(patch_val_str) = patch_tag_split.next() { - if let Ok(patch) = patch_val_str.parse::() { - patch_version = Some(patch); + if let Some(minor_str) = parts.next() { + if let Ok(minor) = minor_str.parse::() { + minor_version = Some(minor); + + if let Some(patch_str) = parts.next() { + if let Ok(patch) = patch_str.parse::() { + patch_version = Some(patch); + + if parts.next().is_some() { + valid_semver = false; + } + } else { + valid_semver = false; + } + } + } else { + valid_semver = false; } } - if let Some(tag_str) = patch_tag_split.next() { - tag = Some(tag_str.to_string()); - } + } else { + valid_semver = false; } + } else { + valid_semver = false; } + + if valid_semver { + tag = parsed_tag; + } else { + major_version = None; + minor_version = None; + patch_version = None; + tag = Some(version.to_string()); + } + } else { + tag = Some(version.to_string()); } Ok(Self { @@ -596,4 +602,69 @@ mod tests { } ); } + + #[test] + fn test_serialize_version_without_patch() { + let json_data = json!({ + "name": "version-test-service", + "version": "8.0" + }); + + let deserialized: Service = from_value(json_data).unwrap(); + let serialized = serde_json::to_value(&deserialized).unwrap(); + + assert_eq!( + serialized, + json!({ + "name": "version-test-service", + "version": "8.0" + }) + ); + } + + #[test] + fn test_deserialize_version_with_tag_in_minor() { + let json_data = json!({ + "name": "minor-tag-service", + "version": "1.2-beta" + }); + + let deserialized: Service = from_value(json_data).unwrap(); + assert_eq!( + deserialized, + Service { + name: "minor-tag-service".to_string(), + namespace: "default".to_string(), + path: None, + build: None, + major_version: Some(1), + minor_version: Some(2), + patch_version: None, + tag: Some("beta".to_string()) + } + ); + } + + #[test] + fn test_deserialize_version_with_tag_in_major() { + let json_data = json!({ + "name": "major-tag-service", + "version": "1-beta" + }); + + let deserialized: Service = from_value(json_data).unwrap(); + assert_eq!( + deserialized, + Service { + name: "major-tag-service".to_string(), + namespace: "default".to_string(), + path: None, + build: None, + major_version: Some(1), + minor_version: None, + patch_version: None, + tag: Some("beta".to_string()) + } + ); + } } From accd5607c4330f6ae5b2e160fe60a3d1587a231e Mon Sep 17 00:00:00 2001 From: Joshua Tracey Date: Mon, 30 Mar 2026 20:12:46 +0100 Subject: [PATCH 4/4] feat: general improvements --- src/environment.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/environment.rs b/src/environment.rs index 1fa2872..5e11866 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -236,7 +236,7 @@ impl Serialize for Service { service.insert("version".to_string(), "latest".to_string()); } else if self.major_version.is_some() { let mut version_str = self.major_version.unwrap().to_string(); - + if let Some(minor) = self.minor_version { version_str.push_str(&format!(".{}", minor)); } @@ -612,7 +612,7 @@ mod tests { let deserialized: Service = from_value(json_data).unwrap(); let serialized = serde_json::to_value(&deserialized).unwrap(); - + assert_eq!( serialized, json!({