From dc044a08a581a0d00d919b97d6bdf69ac6f9d90f Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 7 Aug 2024 04:08:58 +0600 Subject: [PATCH 1/5] Improvements --- src/deploy.rs | 3 ++- src/main.rs | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/deploy.rs b/src/deploy.rs index 74c7895..ae6f083 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -77,7 +77,8 @@ impl<'a> Deploy<'a> { create_docker_image(self.path, envs, &options, &build_options) .await .unwrap_or_else(|e| { - self.log.error(&format!("Error creating image: {}", e)); + self.log + .error(&format!("Error creating Docker image at path {}: {}", self.path, e)); std::process::exit(1); }); diff --git a/src/main.rs b/src/main.rs index 1647f35..59ef2ac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -22,11 +22,12 @@ mod server_config; #[command(version, about = "A CLI app for managing your server.")] struct Cli { #[command(subcommand)] - command: Commands, + command: Command, } +/// Enum representing the various commands that can be executed by the CLI. #[derive(Subcommand)] -enum Commands { +enum Command { /// Show logs Logs, /// Set a configuration variable, e.g, VAR=12 @@ -82,10 +83,10 @@ async fn main() { let cli = Cli::parse(); match &cli.command { - Commands::Logs => { + Command::Logs => { println!("Showing logs..."); } - Commands::ConfigSet { var } => { + Command::ConfigSet { var } => { println!("Setting configuration: {}", var); // Parse `var` into key and value let parts: Vec<&str> = var.split('=').collect(); @@ -97,30 +98,30 @@ async fn main() { log.error("Invalid format. Use KEY=VALUE"); } } - Commands::ConfigGet { key } => { + Command::ConfigGet { key } => { println!("Getting configuration for: {}", key); } - Commands::Run => { + Command::Run => { log.section("Running application"); } - Commands::Deploy => { + Command::Deploy => { log.section("Starting deployment"); } - Commands::Stop => { + Command::Stop => { log.section("Stopping application..."); } - Commands::Destroy => { + Command::Destroy => { println!("Destroying application..."); } - Commands::GitHook { repo } => { + Command::GitHook { repo } => { git.cmd_git_hook(repo); deploy(&log, repo, server_config).await; } - Commands::GitReceivePack { repo } => { + Command::GitReceivePack { repo } => { log.section("... RUKU ..."); git.cmd_git_receive_pack(repo); } - Commands::GitUploadPack { repo } => { + Command::GitUploadPack { repo } => { log.section("... RUKU ..."); git.cmd_git_upload_pack(repo); } From ffe4f2f402bad5b71649aa5a68c1d95e58de9f3d Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 7 Aug 2024 05:29:13 +0600 Subject: [PATCH 2/5] Rukuconfig --- Cargo.lock | 1 + Cargo.toml | 1 + src/container.rs | 17 +++++++++-------- src/deploy.rs | 13 ++++--------- src/main.rs | 40 ++++++++++++++++++++++++++++++++++++---- src/model.rs | 4 ++-- 6 files changed, 53 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73ebdde..4a7571c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1795,6 +1795,7 @@ dependencies = [ "home", "nixpacks", "serde", + "serde_yaml", "tokio", ] diff --git a/Cargo.toml b/Cargo.toml index d4cf168..324a684 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ envy = "0.4.2" home = "0.5.9" nixpacks = "1.26.0" serde = { version = "1.0.204", features = ["derive"] } +serde_yaml = "0.9.34" tokio = { version = "1.39.2", features = ["rt", "rt-multi-thread", "macros"] } diff --git a/src/container.rs b/src/container.rs index 397ceba..f2bd3c0 100644 --- a/src/container.rs +++ b/src/container.rs @@ -1,24 +1,25 @@ +use std::collections::HashMap; +use std::str::FromStr; + use bollard::container::{CreateContainerOptions, ListContainersOptions, StartContainerOptions}; +use bollard::Docker; use bollard::models::{ ContainerCreateResponse, ContainerStateStatusEnum, ContainerSummary, HostConfig, PortBinding, PortMap, }; -use bollard::Docker; -use std::collections::HashMap; -use std::str::FromStr; use crate::logger::Logger; use crate::misc::get_image_name_with_version; -use crate::model::Config; +use crate::model::RukuConfig; pub struct Container<'a> { log: &'a Logger, name: &'a str, docker: &'a Docker, - config: &'a Config, + config: &'a RukuConfig, } impl<'a> Container<'a> { - pub fn new(log: &'a Logger, name: &'a str, docker: &'a Docker, config: &'a Config) -> Container<'a> { + pub fn new(log: &'a Logger, name: &'a str, docker: &'a Docker, config: &'a RukuConfig) -> Container<'a> { Container { log, name, @@ -133,14 +134,14 @@ impl<'a> Container<'a> { platform: None, }; - let exposed_port = format!("{}/tcp", self.config.port.unwrap()); + let exposed_port = format!("{}/tcp", self.config.port); let mut host_config = HostConfig::default(); let mut port_bindings = PortMap::new(); port_bindings.insert( exposed_port.clone(), Some(vec![PortBinding { host_ip: None, - host_port: Some(self.config.port.unwrap().to_string()), + host_port: Some(self.config.port.to_string()), }]), ); host_config.port_bindings = Some(port_bindings); diff --git a/src/deploy.rs b/src/deploy.rs index ae6f083..3cac70c 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -1,17 +1,17 @@ use nixpacks::create_docker_image; use nixpacks::nixpacks::builder::docker::DockerBuilderOptions; -use nixpacks::nixpacks::plan::{generator::GeneratePlanOptions, BuildPlan}; +use nixpacks::nixpacks::plan::{BuildPlan, generator::GeneratePlanOptions}; use crate::container::Container; use crate::logger::Logger; use crate::misc::get_image_name_with_version; -use crate::model::Config; +use crate::model::RukuConfig; pub struct Deploy<'a> { log: &'a Logger, name: &'a str, path: &'a str, - config: &'a Config, + config: &'a RukuConfig, container: &'a Container<'a>, } @@ -20,7 +20,7 @@ impl<'a> Deploy<'a> { log: &'a Logger, name: &'a str, path: &'a str, - config: &'a Config, + config: &'a RukuConfig, container: &'a Container<'a>, ) -> Deploy<'a> { Deploy { @@ -33,11 +33,6 @@ impl<'a> Deploy<'a> { } pub async fn run(&self) { - if self.config.port.is_none() { - self.log.error("No port specified, skipping deployment"); - std::process::exit(1); - } - self.log.step(&format!("Running from {}", self.path)); // Nix pack diff --git a/src/main.rs b/src/main.rs index 59ef2ac..fdaf7e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +use std::fs; + use bollard::Docker; use clap::{Parser, Subcommand}; @@ -8,7 +10,7 @@ use crate::container::Container; use crate::deploy::Deploy; use crate::git::Git; use crate::misc::sanitize_app_name; -use crate::model::Config; +use crate::model::RukuConfig; mod container; mod deploy; @@ -115,7 +117,7 @@ async fn main() { } Command::GitHook { repo } => { git.cmd_git_hook(repo); - deploy(&log, repo, server_config).await; + deploy(&log, repo, &server_config).await; } Command::GitReceivePack { repo } => { log.section("... RUKU ..."); @@ -128,9 +130,9 @@ async fn main() { } } -async fn deploy(log: &Logger, repo: &str, server_config: ServerConfig) { +async fn deploy(log: &Logger, repo: &str, server_config: &ServerConfig) { log.section("Deploying application"); - let config = Config::default(); + let config = get_ruku_config(log, repo, server_config); let docker = get_docker(log).await; let app = sanitize_app_name(repo); @@ -164,3 +166,33 @@ async fn load_docker(log: &Logger) -> Docker { std::process::exit(1); }) } + +fn get_ruku_config(log: &Logger, repo: &str, server_config: &ServerConfig) -> RukuConfig { + let repo_path = server_config.apps_root.join(&repo); + + // Check for the presence of ruku.yml file + let config_path = repo_path.join("ruku.yml"); + if !config_path.exists() { + log.error("ruku.yml file is missing in the repository"); + std::process::exit(1); + } + + // Parse the ruku.yml file + let config_content = fs::read_to_string(&config_path).unwrap_or_else(|e| { + log.error(&format!("Error reading ruku.yml file: {}", e)); + std::process::exit(1); + }); + + let config: RukuConfig = serde_yaml::from_str(&config_content).unwrap_or_else(|e| { + log.error(&format!("Error parsing ruku.yml file: {}", e)); + std::process::exit(1); + }); + + // Validate that there is an entry with port: int + if config.port <= 0 { + log.error("Invalid port number in ruku.yml file"); + std::process::exit(1); + } + + config +} diff --git a/src/model.rs b/src/model.rs index fc24880..d1761c9 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,7 @@ use serde::Deserialize; #[derive(Default, Deserialize)] -pub struct Config { - pub port: Option, +pub struct RukuConfig { + pub port: u16, pub version: Option, } From c1849f8c8fb82f2e99449badbcc66571d16217aa Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 7 Aug 2024 17:31:27 +0600 Subject: [PATCH 3/5] Update main.rs --- src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.rs b/src/main.rs index fdaf7e4..48797ee 100644 --- a/src/main.rs +++ b/src/main.rs @@ -121,6 +121,7 @@ async fn main() { } Command::GitReceivePack { repo } => { log.section("... RUKU ..."); + let _ = get_ruku_config(&log, repo, &server_config); git.cmd_git_receive_pack(repo); } Command::GitUploadPack { repo } => { From 28886a445d751d4081fcb5e42de5966b04e5b8fb Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 7 Aug 2024 18:17:59 +0600 Subject: [PATCH 4/5] Validate open port --- Cargo.lock | 58 +++++++++++++++++++++++++++++++++++++--------------- Cargo.toml | 4 ++-- src/model.rs | 13 +++++++++++- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a7571c..21c7fb8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -757,12 +757,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dotenvy" -version = "0.15.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" - [[package]] name = "encode_unicode" version = "0.3.6" @@ -791,15 +785,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "envy" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" -dependencies = [ - "serde", -] - [[package]] name = "equivalent" version = "1.0.1" @@ -1599,6 +1584,15 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +[[package]] +name = "port-selector" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd119ef551a50cd8939f0ff93bd062891f7b0dbb771b4a05df8a9c13aebaff68" +dependencies = [ + "rand 0.8.5", +] + [[package]] name = "portpicker" version = "0.1.1" @@ -1790,13 +1784,13 @@ dependencies = [ "clap", "cmd_lib", "colored", - "dotenvy", - "envy", "home", "nixpacks", + "port-selector", "serde", "serde_yaml", "tokio", + "validator", ] [[package]] @@ -2290,6 +2284,36 @@ dependencies = [ "getrandom", ] +[[package]] +name = "validator" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db79c75af171630a3148bd3e6d7c4f42b6a9a014c2945bc5ed0020cbb8d9478e" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55591299b7007f551ed1eb79a684af7672c19c3193fb9e0a31936987bb2438ec" +dependencies = [ + "darling", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.68", +] + [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index 324a684..139d11e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,10 +13,10 @@ bollard = "0.17.0" clap = { version = "4.5.13", features = ["derive"] } cmd_lib = "1.9.4" colored = "2.1.0" -dotenvy = "0.15.7" -envy = "0.4.2" home = "0.5.9" nixpacks = "1.26.0" +port-selector = "0.1.6" serde = { version = "1.0.204", features = ["derive"] } serde_yaml = "0.9.34" tokio = { version = "1.39.2", features = ["rt", "rt-multi-thread", "macros"] } +validator = { version = "0.18.1", features = ["derive"] } diff --git a/src/model.rs b/src/model.rs index d1761c9..b5844a3 100644 --- a/src/model.rs +++ b/src/model.rs @@ -1,7 +1,18 @@ +use port_selector::is_free; use serde::Deserialize; +use validator::{Validate, ValidationError}; -#[derive(Default, Deserialize)] +#[derive(Debug, Validate, Deserialize)] pub struct RukuConfig { + #[validate(range(min = 1024, max = 65535), custom(function = "validate_port"))] pub port: u16, + #[validate(length(min = 1, max = 20))] pub version: Option, } + +fn validate_port(port: u16) -> Result<(), ValidationError> { + if !is_free(port) { + return Err(ValidationError::new("port is already in use")); + } + Ok(()) +} From 998d1bb8821b153c795882330fb45b54c25be565 Mon Sep 17 00:00:00 2001 From: Hasan Date: Wed, 7 Aug 2024 18:18:53 +0600 Subject: [PATCH 5/5] Validate ruku config --- src/container.rs | 2 +- src/deploy.rs | 2 +- src/main.rs | 13 ++++++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/container.rs b/src/container.rs index f2bd3c0..3da4d5a 100644 --- a/src/container.rs +++ b/src/container.rs @@ -2,10 +2,10 @@ use std::collections::HashMap; use std::str::FromStr; use bollard::container::{CreateContainerOptions, ListContainersOptions, StartContainerOptions}; -use bollard::Docker; use bollard::models::{ ContainerCreateResponse, ContainerStateStatusEnum, ContainerSummary, HostConfig, PortBinding, PortMap, }; +use bollard::Docker; use crate::logger::Logger; use crate::misc::get_image_name_with_version; diff --git a/src/deploy.rs b/src/deploy.rs index 3cac70c..60ffc27 100644 --- a/src/deploy.rs +++ b/src/deploy.rs @@ -1,6 +1,6 @@ use nixpacks::create_docker_image; use nixpacks::nixpacks::builder::docker::DockerBuilderOptions; -use nixpacks::nixpacks::plan::{BuildPlan, generator::GeneratePlanOptions}; +use nixpacks::nixpacks::plan::{generator::GeneratePlanOptions, BuildPlan}; use crate::container::Container; use crate::logger::Logger; diff --git a/src/main.rs b/src/main.rs index 48797ee..c568fa3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ use std::fs; use bollard::Docker; use clap::{Parser, Subcommand}; +use validator::Validate; use logger::Logger; use server_config::ServerConfig; @@ -189,11 +190,13 @@ fn get_ruku_config(log: &Logger, repo: &str, server_config: &ServerConfig) -> Ru std::process::exit(1); }); - // Validate that there is an entry with port: int - if config.port <= 0 { - log.error("Invalid port number in ruku.yml file"); - std::process::exit(1); - } + match config.validate() { + Ok(_) => (), + Err(e) => { + log.error(&format!("Error validating ruku.yml file: {}", e)); + std::process::exit(1); + } + }; config }