feat: LampScore implement dockerfile generation and image building
- Added `build_dockerfile` function to generate a Dockerfile based on the LAMP stack for the given project. - Implemented `build_docker_image` to execute the docker build command and create the image. - Configured user and permissions for apache. - Included necessary apache configuration for security. - Added error handling for docker build failures. - Exposed port 80 for external access. - Added basic serialization to Config struct.
This commit is contained in:
		
							parent
							
								
									065e3904b8
								
							
						
					
					
						commit
						16a665241e
					
				
							
								
								
									
										23
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										23
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -833,6 +833,28 @@ version = "0.3.3" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "dockerfile_builder" | ||||
| version = "0.1.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0ac372e31c7dd054d0fc69ca96ca36ee8d1cf79881683ad6f783c47aba3dc6e2" | ||||
| dependencies = [ | ||||
|  "dockerfile_builder_macros", | ||||
|  "eyre", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "dockerfile_builder_macros" | ||||
| version = "0.1.5" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "b627d9019ce257916c7ada6f233cf22e1e5246b6d9426b20610218afb7fd3ec9" | ||||
| dependencies = [ | ||||
|  "eyre", | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "dyn-clone" | ||||
| version = "1.0.19" | ||||
| @ -1363,6 +1385,7 @@ dependencies = [ | ||||
|  "cidr", | ||||
|  "derive-new", | ||||
|  "directories", | ||||
|  "dockerfile_builder", | ||||
|  "env_logger", | ||||
|  "harmony_macros", | ||||
|  "harmony_types", | ||||
|  | ||||
| @ -36,3 +36,4 @@ non-blank-string-rs = "1.0.4" | ||||
| k3d-rs = { path = "../k3d" } | ||||
| directories = "6.0.0" | ||||
| lazy_static = "1.5.0" | ||||
| dockerfile_builder = "0.1.5" | ||||
|  | ||||
| @ -57,8 +57,10 @@ impl Topology for HAClusterTopology { | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl K8sclient for HAClusterTopology { | ||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, kube::Error> { | ||||
|         Ok(Arc::new(K8sClient::try_default().await?)) | ||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, String> { | ||||
|         Ok(Arc::new( | ||||
|             K8sClient::try_default().await.map_err(|e| e.to_string())?, | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| use std::process::Command; | ||||
| use std::{process::Command, sync::Arc}; | ||||
| 
 | ||||
| use async_trait::async_trait; | ||||
| use inquire::Confirm; | ||||
| @ -13,10 +13,10 @@ use crate::{ | ||||
|     topology::LocalhostTopology, | ||||
| }; | ||||
| 
 | ||||
| use super::{HelmCommand, Topology, k8s::K8sClient}; | ||||
| use super::{HelmCommand, K8sclient, Topology, k8s::K8sClient}; | ||||
| 
 | ||||
| struct K8sState { | ||||
|     _client: K8sClient, | ||||
|     client: Arc<K8sClient>, | ||||
|     _source: K8sSource, | ||||
|     message: String, | ||||
| } | ||||
| @ -29,6 +29,23 @@ pub struct K8sAnywhereTopology { | ||||
|     k8s_state: OnceCell<Option<K8sState>>, | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl K8sclient for K8sAnywhereTopology { | ||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, String> { | ||||
|         let state = match self.k8s_state.get() { | ||||
|             Some(state) => state, | ||||
|             None => return Err("K8s state not initialized yet".to_string()), | ||||
|         }; | ||||
| 
 | ||||
|         let state = match state { | ||||
|             Some(state) => state, | ||||
|             None => return Err("K8s client initialized but empty".to_string()), | ||||
|         }; | ||||
| 
 | ||||
|         Ok(state.client.clone()) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl K8sAnywhereTopology { | ||||
|     pub fn new() -> Self { | ||||
|         Self { | ||||
| @ -124,7 +141,7 @@ impl K8sAnywhereTopology { | ||||
|         let k3d = k3d_rs::K3d::new(k3d_score.installation_path, Some(k3d_score.cluster_name)); | ||||
|         let state = match k3d.get_client().await { | ||||
|             Ok(client) => K8sState { | ||||
|                 _client: K8sClient::new(client), | ||||
|                 client: Arc::new(K8sClient::new(client)), | ||||
|                 _source: K8sSource::LocalK3d, | ||||
|                 message: "Successfully installed K3D cluster and acquired client".to_string(), | ||||
|             }, | ||||
|  | ||||
| @ -42,8 +42,8 @@ pub struct NetworkDomain { | ||||
|     pub name: String, | ||||
| } | ||||
| #[async_trait] | ||||
| pub trait K8sclient: Send + Sync + std::fmt::Debug { | ||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, kube::Error>; | ||||
| pub trait K8sclient: Send + Sync { | ||||
|     async fn k8s_client(&self) -> Result<Arc<K8sClient>, String>; | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| use std::path::{Path, PathBuf}; | ||||
| 
 | ||||
| use async_trait::async_trait; | ||||
| use log::info; | ||||
| use serde::Serialize; | ||||
| 
 | ||||
| use crate::{ | ||||
| @ -35,9 +36,11 @@ impl Default for LAMPConfig { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology> Score<T> for LAMPScore { | ||||
| impl<T: Topology + K8sclient> Score<T> for LAMPScore { | ||||
|     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||
|         todo!() | ||||
|         Box::new(LAMPInterpret { | ||||
|             score: self.clone(), | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     fn name(&self) -> String { | ||||
| @ -57,11 +60,23 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret { | ||||
|         inventory: &Inventory, | ||||
|         topology: &T, | ||||
|     ) -> Result<Outcome, InterpretError> { | ||||
|         let image_name = match self.build_docker_image() { | ||||
|             Ok(name) => name, | ||||
|             Err(e) => { | ||||
|                 return Err(InterpretError::new(format!( | ||||
|                     "Could not build LAMP docker image {e}" | ||||
|                 ))); | ||||
|             } | ||||
|         }; | ||||
|         info!("LAMP docker image built {image_name}"); | ||||
| 
 | ||||
|         let deployment_score = K8sDeploymentScore { | ||||
|             name: <LAMPScore as Score<T>>::name(&self.score), | ||||
|             image: "local_image".to_string(), | ||||
|             image: image_name, | ||||
|         }; | ||||
| 
 | ||||
|         info!("LAMP deployment_score {deployment_score:?}"); | ||||
|         todo!(); | ||||
|         deployment_score | ||||
|             .create_interpret() | ||||
|             .execute(inventory, topology) | ||||
| @ -85,3 +100,160 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret { | ||||
|         todo!() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| use dockerfile_builder::Dockerfile; | ||||
| use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR}; | ||||
| use std::fs; | ||||
| 
 | ||||
| impl LAMPInterpret { | ||||
|     pub fn build_dockerfile( | ||||
|         &self, | ||||
|         score: &LAMPScore, | ||||
|     ) -> Result<PathBuf, Box<dyn std::error::Error>> { | ||||
|         let mut dockerfile = Dockerfile::new(); | ||||
| 
 | ||||
|         // Use the PHP version from the score to determine the base image
 | ||||
|         let php_version = score.php_version.to_string(); | ||||
|         let php_major_minor = php_version | ||||
|             .split('.') | ||||
|             .take(2) | ||||
|             .collect::<Vec<&str>>() | ||||
|             .join("."); | ||||
| 
 | ||||
|         // Base image selection - using official PHP image with Apache
 | ||||
|         dockerfile.push(FROM::from(format!("php:{}-apache", php_major_minor))); | ||||
| 
 | ||||
|         // Set environment variables for PHP configuration
 | ||||
|         dockerfile.push(ENV::from("PHP_MEMORY_LIMIT=256M")); | ||||
|         dockerfile.push(ENV::from("PHP_MAX_EXECUTION_TIME=30")); | ||||
|         dockerfile.push(ENV::from( | ||||
|             "PHP_ERROR_REPORTING=E_ERROR | E_WARNING | E_PARSE", | ||||
|         )); | ||||
| 
 | ||||
|         // Install necessary PHP extensions and dependencies
 | ||||
|         dockerfile.push(RUN::from( | ||||
|             "apt-get update && \ | ||||
|              apt-get install -y --no-install-recommends \ | ||||
|                 libfreetype6-dev \ | ||||
|                 libjpeg62-turbo-dev \ | ||||
|                 libpng-dev \ | ||||
|                 libzip-dev \ | ||||
|                 unzip \ | ||||
|                 && apt-get clean \ | ||||
|                 && rm -rf /var/lib/apt/lists/*",
 | ||||
|         )); | ||||
| 
 | ||||
|         dockerfile.push(RUN::from( | ||||
|             "docker-php-ext-configure gd --with-freetype --with-jpeg && \ | ||||
|              docker-php-ext-install -j$(nproc) \ | ||||
|                 gd \ | ||||
|                 mysqli \ | ||||
|                 pdo_mysql \ | ||||
|                 zip \ | ||||
|                 opcache",
 | ||||
|         )); | ||||
| 
 | ||||
|         // Copy PHP configuration
 | ||||
|         dockerfile.push(RUN::from("mkdir -p /usr/local/etc/php/conf.d/")); | ||||
| 
 | ||||
|         // Create and copy a custom PHP configuration
 | ||||
|         let php_config = r#" | ||||
| memory_limit = ${PHP_MEMORY_LIMIT} | ||||
| max_execution_time = ${PHP_MAX_EXECUTION_TIME} | ||||
| error_reporting = ${PHP_ERROR_REPORTING} | ||||
| display_errors = Off | ||||
| log_errors = On | ||||
| error_log = /dev/stderr | ||||
| date.timezone = UTC | ||||
| 
 | ||||
| ; Opcache configuration for production | ||||
| opcache.enable=1 | ||||
| opcache.memory_consumption=128 | ||||
| opcache.interned_strings_buffer=8 | ||||
| opcache.max_accelerated_files=4000 | ||||
| opcache.revalidate_freq=2 | ||||
| opcache.fast_shutdown=1 | ||||
| "#;
 | ||||
| 
 | ||||
|         // Save this configuration to a temporary file within the project root
 | ||||
|         let config_path = Path::new(&score.config.project_root).join("docker-php.ini"); | ||||
|         fs::write(&config_path, php_config)?; | ||||
| 
 | ||||
|         // Reference the file within the Docker context (where the build runs)
 | ||||
|         dockerfile.push(COPY::from( | ||||
|             "docker-php.ini /usr/local/etc/php/conf.d/docker-php.ini", | ||||
|         )); | ||||
| 
 | ||||
|         // Security hardening
 | ||||
|         dockerfile.push(RUN::from( | ||||
|             "a2enmod headers && \ | ||||
|              a2enmod rewrite && \ | ||||
|              sed -i 's/ServerTokens OS/ServerTokens Prod/' /etc/apache2/conf-enabled/security.conf && \ | ||||
|              sed -i 's/ServerSignature On/ServerSignature Off/' /etc/apache2/conf-enabled/security.conf" | ||||
|         )); | ||||
| 
 | ||||
|         // Create a dedicated user for running Apache
 | ||||
|         dockerfile.push(RUN::from( | ||||
|             "groupadd -g 1000 appuser && \ | ||||
|              useradd -u 1000 -g appuser -m -s /bin/bash appuser && \ | ||||
|              chown -R appuser:appuser /var/www/html",
 | ||||
|         )); | ||||
| 
 | ||||
|         // Set the working directory
 | ||||
|         dockerfile.push(WORKDIR::from("/var/www/html")); | ||||
| 
 | ||||
|         // Copy application code from the project root to the container
 | ||||
|         // Note: In Dockerfile, the COPY context is relative to the build context
 | ||||
|         // We'll handle the actual context in the build_docker_image method
 | ||||
|         dockerfile.push(COPY::from(". /var/www/html")); | ||||
| 
 | ||||
|         // Fix permissions
 | ||||
|         dockerfile.push(RUN::from("chown -R appuser:appuser /var/www/html")); | ||||
| 
 | ||||
|         // Expose Apache port
 | ||||
|         dockerfile.push(EXPOSE::from("80/tcp")); | ||||
| 
 | ||||
|         // Set the default command
 | ||||
|         dockerfile.push(CMD::from("apache2-foreground")); | ||||
| 
 | ||||
|         // Save the Dockerfile to disk in the project root
 | ||||
|         let dockerfile_path = Path::new(&score.config.project_root).join("Dockerfile"); | ||||
|         fs::write(&dockerfile_path, dockerfile.to_string())?; | ||||
| 
 | ||||
|         Ok(dockerfile_path) | ||||
|     } | ||||
| 
 | ||||
|     pub fn build_docker_image(&self) -> Result<String, Box<dyn std::error::Error>> { | ||||
|         info!("Generating Dockerfile"); | ||||
|         let dockerfile = self.build_dockerfile(&self.score)?; | ||||
| 
 | ||||
|         info!( | ||||
|             "Building Docker image with file {} from root {}", | ||||
|             dockerfile.to_string_lossy(), | ||||
|             self.score.config.project_root.to_string_lossy() | ||||
|         ); | ||||
|         let image_name = format!("{}-php-apache", self.score.name); | ||||
|         let project_root = &self.score.config.project_root; | ||||
| 
 | ||||
|         let output = std::process::Command::new("docker") | ||||
|             .args([ | ||||
|                 "build", | ||||
|                 "--file", | ||||
|                 dockerfile.to_str().unwrap(), | ||||
|                 "-t", | ||||
|                 &image_name, | ||||
|                 project_root.to_str().unwrap(), | ||||
|             ]) | ||||
|             .output()?; | ||||
| 
 | ||||
|         if !output.status.success() { | ||||
|             return Err(format!( | ||||
|                 "Failed to build Docker image: {}", | ||||
|                 String::from_utf8_lossy(&output.stderr) | ||||
|             ) | ||||
|             .into()); | ||||
|         } | ||||
| 
 | ||||
|         Ok(image_name) | ||||
|     } | ||||
| } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user