feat: added app to fill a pvc in 1GB increments #24
							
								
								
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1020,8 +1020,8 @@ dependencies = [ | |||||||
|  "cidr", |  "cidr", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "harmony", |  "harmony", | ||||||
|  |  "harmony_cli", | ||||||
|  "harmony_macros", |  "harmony_macros", | ||||||
|  "harmony_tui", |  | ||||||
|  "harmony_types", |  "harmony_types", | ||||||
|  "log", |  "log", | ||||||
|  "tokio", |  "tokio", | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ publish = false | |||||||
| 
 | 
 | ||||||
| [dependencies] | [dependencies] | ||||||
| harmony = { path = "../../harmony" } | harmony = { path = "../../harmony" } | ||||||
| harmony_tui = { path = "../../harmony_tui" } | harmony_cli = { path = "../../harmony_cli" } | ||||||
| harmony_types = { path = "../../harmony_types" } | harmony_types = { path = "../../harmony_types" } | ||||||
| cidr = { workspace = true } | cidr = { workspace = true } | ||||||
| tokio = { workspace = true } | tokio = { workspace = true } | ||||||
|  | |||||||
| @ -26,5 +26,5 @@ async fn main() { | |||||||
|     .await |     .await | ||||||
|     .unwrap(); |     .unwrap(); | ||||||
|     maestro.register_all(vec![Box::new(lamp_stack)]); |     maestro.register_all(vec![Box::new(lamp_stack)]); | ||||||
|     harmony_tui::init(maestro).await.unwrap(); |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
|  | |||||||
| } | } | ||||||
|  | |||||||
| @ -6,10 +6,12 @@ use crate::topology::{HelmCommand, Topology}; | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use helm_wrapper_rs; | use helm_wrapper_rs; | ||||||
| use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; | use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor}; | ||||||
|  | use log::info; | ||||||
| pub use non_blank_string_rs::NonBlankString; | pub use non_blank_string_rs::NonBlankString; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::path::Path; | use std::path::Path; | ||||||
|  | use std::str::FromStr; | ||||||
| use temp_file::TempFile; | use temp_file::TempFile; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| @ -20,6 +22,10 @@ pub struct HelmChartScore { | |||||||
|     pub chart_version: Option<NonBlankString>, |     pub chart_version: Option<NonBlankString>, | ||||||
|     pub values_overrides: Option<HashMap<NonBlankString, String>>, |     pub values_overrides: Option<HashMap<NonBlankString, String>>, | ||||||
|     pub values_yaml: Option<String>, |     pub values_yaml: Option<String>, | ||||||
|  |     pub create_namespace: bool, | ||||||
|  | 
 | ||||||
|  |     /// Wether to run `helm upgrade --install` under the hood or only install when not present
 | ||||||
|  |     pub install_only: bool, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | impl<T: Topology + HelmCommand> Score<T> for HelmChartScore { | ||||||
| @ -62,6 +68,47 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | |||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         let helm_executor = DefaultHelmExecutor::new(); |         let helm_executor = DefaultHelmExecutor::new(); | ||||||
|  | 
 | ||||||
|  |         let mut helm_options = Vec::new(); | ||||||
|  |         if self.score.create_namespace { | ||||||
|  |             helm_options.push(NonBlankString::from_str("--create-namespace").unwrap()); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if self.score.install_only { | ||||||
|  |             let chart_list = match helm_executor.list(Some(ns)) { | ||||||
|  |                 Ok(charts) => charts, | ||||||
|  |                 Err(e) => { | ||||||
|  |                     return Err(InterpretError::new(format!( | ||||||
|  |                         "Failed to list scores in namespace {:?} because of error : {}", | ||||||
|  |                         self.score.namespace, e | ||||||
|  |                     ))); | ||||||
|  |                 } | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             if chart_list | ||||||
|  |                 .iter() | ||||||
|  |                 .any(|item| item.name == self.score.release_name.to_string()) | ||||||
|  |             { | ||||||
|  |                 info!( | ||||||
|  |                     "Release '{}' already exists in namespace '{}'. Skipping installation as install_only is true.", | ||||||
|  |                     self.score.release_name, ns | ||||||
|  |                 ); | ||||||
|  | 
 | ||||||
|  |                 return Ok(Outcome::new( | ||||||
|  |                     InterpretStatus::SUCCESS, | ||||||
|  |                     format!( | ||||||
|  |                         "Helm Chart '{}' already installed to namespace {ns} and install_only=true", | ||||||
|  |                         self.score.release_name | ||||||
|  |                     ), | ||||||
|  |                 )); | ||||||
|  |             } else { | ||||||
|  |                 info!( | ||||||
|  |                     "Release '{}' not found in namespace '{}'. Proceeding with installation.", | ||||||
|  |                     self.score.release_name, ns | ||||||
|  |                 ); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         let res = helm_executor.install_or_upgrade( |         let res = helm_executor.install_or_upgrade( | ||||||
|             &ns, |             &ns, | ||||||
|             &self.score.release_name, |             &self.score.release_name, | ||||||
| @ -69,7 +116,7 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | |||||||
|             self.score.chart_version.as_ref(), |             self.score.chart_version.as_ref(), | ||||||
|             self.score.values_overrides.as_ref(), |             self.score.values_overrides.as_ref(), | ||||||
|             yaml_path, |             yaml_path, | ||||||
|             None, |             Some(&helm_options), | ||||||
|         ); |         ); | ||||||
| 
 | 
 | ||||||
|         let status = match res { |         let status = match res { | ||||||
|  | |||||||
| @ -1,2 +1,3 @@ | |||||||
| pub mod deployment; | pub mod deployment; | ||||||
|  | pub mod namespace; | ||||||
| pub mod resource; | pub mod resource; | ||||||
|  | |||||||
							
								
								
									
										46
									
								
								harmony/src/modules/k8s/namespace.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								harmony/src/modules/k8s/namespace.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | |||||||
|  | use k8s_openapi::api::core::v1::Namespace; | ||||||
|  | use non_blank_string_rs::NonBlankString; | ||||||
|  | use serde::Serialize; | ||||||
|  | use serde_json::json; | ||||||
|  | 
 | ||||||
|  | use crate::{ | ||||||
|  |     interpret::Interpret, | ||||||
|  |     score::Score, | ||||||
|  |     topology::{K8sclient, Topology}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | #[derive(Debug, Clone, Serialize)] | ||||||
|  | pub struct K8sNamespaceScore { | ||||||
|  |     pub name: Option<NonBlankString>, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl<T: Topology + K8sclient> Score<T> for K8sNamespaceScore { | ||||||
|  |     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||||
|  |         let name = match &self.name { | ||||||
|  |             Some(name) => name, | ||||||
|  |             None => todo!( | ||||||
|  |                 "Return NoOp interpret when no namespace specified or something that makes sense" | ||||||
|  |             ), | ||||||
|  |         }; | ||||||
|  |         let _namespace: Namespace = serde_json::from_value(json!( | ||||||
|  |             { | ||||||
|  |                 "apiVersion": "v1", | ||||||
|  |                 "kind": "Namespace", | ||||||
|  |                 "metadata": { | ||||||
|  |                     "name": name, | ||||||
|  |                 }, | ||||||
|  |             } | ||||||
|  |         )) | ||||||
|  |         .unwrap(); | ||||||
|  |         todo!( | ||||||
|  |             "We currently only support namespaced ressources (see Scope = NamespaceResourceScope)" | ||||||
|  |         ); | ||||||
|  |         // Box::new(K8sResourceInterpret {
 | ||||||
|  |         //     score: K8sResourceScore::single(namespace.clone()),
 | ||||||
|  |         // })
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn name(&self) -> String { | ||||||
|  |         "K8sNamespaceScore".to_string() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -1,9 +1,15 @@ | |||||||
|  | use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR}; | ||||||
|  | use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder}; | ||||||
|  | use non_blank_string_rs::NonBlankString; | ||||||
|  | use std::fs; | ||||||
| use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||||
|  | use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::info; | use log::info; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
|  | use crate::topology::HelmCommand; | ||||||
| use crate::{ | use crate::{ | ||||||
|     data::{Id, Version}, |     data::{Id, Version}, | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
| @ -13,6 +19,8 @@ use crate::{ | |||||||
|     topology::{K8sclient, Topology, Url}, |     topology::{K8sclient, Topology, Url}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | use super::helm::chart::HelmChartScore; | ||||||
|  | 
 | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| pub struct LAMPScore { | pub struct LAMPScore { | ||||||
|     pub name: String, |     pub name: String, | ||||||
| @ -36,10 +44,11 @@ impl Default for LAMPConfig { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + K8sclient> Score<T> for LAMPScore { | impl<T: Topology + K8sclient + HelmCommand> Score<T> for LAMPScore { | ||||||
|     fn create_interpret(&self) -> Box<dyn Interpret<T>> { |     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||||
|         Box::new(LAMPInterpret { |         Box::new(LAMPInterpret { | ||||||
|             score: self.clone(), |             score: self.clone(), | ||||||
|  |             namespace: "harmony-lamp".to_string(), | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -51,10 +60,11 @@ impl<T: Topology + K8sclient> Score<T> for LAMPScore { | |||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct LAMPInterpret { | pub struct LAMPInterpret { | ||||||
|     score: LAMPScore, |     score: LAMPScore, | ||||||
|  |     namespace: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret { | impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for LAMPInterpret { | ||||||
|     async fn execute( |     async fn execute( | ||||||
|         &self, |         &self, | ||||||
|         inventory: &Inventory, |         inventory: &Inventory, | ||||||
| @ -70,18 +80,23 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret { | |||||||
|         }; |         }; | ||||||
|         info!("LAMP docker image built {image_name}"); |         info!("LAMP docker image built {image_name}"); | ||||||
| 
 | 
 | ||||||
|  |         info!("Deploying database"); | ||||||
|  |         self.deploy_database(inventory, topology).await?; | ||||||
|  | 
 | ||||||
|         let deployment_score = K8sDeploymentScore { |         let deployment_score = K8sDeploymentScore { | ||||||
|             name: <LAMPScore as Score<T>>::name(&self.score), |             name: <LAMPScore as Score<T>>::name(&self.score), | ||||||
|             image: image_name, |             image: image_name, | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         info!("LAMP deployment_score {deployment_score:?}"); |  | ||||||
|         todo!(); |  | ||||||
|         deployment_score |         deployment_score | ||||||
|             .create_interpret() |             .create_interpret() | ||||||
|             .execute(inventory, topology) |             .execute(inventory, topology) | ||||||
|             .await?; |             .await?; | ||||||
|         todo!() | 
 | ||||||
|  |         info!("LAMP deployment_score {deployment_score:?}"); | ||||||
|  |         todo!("1. Use HelmChartScore to deploy mariadb
 | ||||||
|  |             2. Use deploymentScore to deploy lamp docker container | ||||||
|  |             3. for remote clusters, push the image to some registry (use nationtech's for demos? push to the cluster's registry?)");
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
| @ -101,15 +116,31 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret { | |||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR}; |  | ||||||
| use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder}; |  | ||||||
| use std::fs; |  | ||||||
| 
 |  | ||||||
| impl LAMPInterpret { | impl LAMPInterpret { | ||||||
|     pub fn build_dockerfile( |     async fn deploy_database<T: Topology + K8sclient + HelmCommand>( | ||||||
|         &self, |         &self, | ||||||
|         score: &LAMPScore, |         inventory: &Inventory, | ||||||
|     ) -> Result<PathBuf, Box<dyn std::error::Error>> { |         topology: &T, | ||||||
|  |     ) -> Result<Outcome, InterpretError> { | ||||||
|  |         let score = HelmChartScore { | ||||||
|  |             namespace: self.get_namespace(), | ||||||
|  |             release_name: NonBlankString::from_str(&format!("{}-database", self.score.name)) | ||||||
|  |                 .unwrap(), | ||||||
|  |             chart_name: NonBlankString::from_str( | ||||||
|  |                 "oci://registry-1.docker.io/bitnamicharts/mariadb", | ||||||
|  |             ) | ||||||
|  |             .unwrap(), | ||||||
|  |             chart_version: None, | ||||||
|  |             values_overrides: None, | ||||||
|  |             create_namespace: true, | ||||||
|  |             install_only: true, | ||||||
|  |             values_yaml: None, | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         score.create_interpret().execute(inventory, topology).await | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     fn build_dockerfile(&self, score: &LAMPScore) -> Result<PathBuf, Box<dyn std::error::Error>> { | ||||||
|         let mut dockerfile = Dockerfile::new(); |         let mut dockerfile = Dockerfile::new(); | ||||||
| 
 | 
 | ||||||
|         // Use the PHP version from the score to determine the base image
 |         // Use the PHP version from the score to determine the base image
 | ||||||
| @ -260,4 +291,8 @@ opcache.fast_shutdown=1 | |||||||
| 
 | 
 | ||||||
|         Ok(image_name) |         Ok(image_name) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fn get_namespace(&self) -> Option<NonBlankString> { | ||||||
|  |         Some(NonBlankString::from_str(&self.namespace).unwrap()) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,14 +4,14 @@ pub mod modules; | |||||||
| 
 | 
 | ||||||
| pub use config::Config; | pub use config::Config; | ||||||
| pub use error::Error; | pub use error::Error; | ||||||
| #[cfg(test)] | 
 | ||||||
| mod test { | #[cfg(e2e_test)] | ||||||
|  | mod e2e_test { | ||||||
|     use opnsense_config_xml::StaticMap; |     use opnsense_config_xml::StaticMap; | ||||||
|     use std::net::Ipv4Addr; |     use std::net::Ipv4Addr; | ||||||
| 
 | 
 | ||||||
|     use crate::Config; |     use crate::Config; | ||||||
| 
 | 
 | ||||||
|     #[cfg(opnsenseendtoend)] |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn test_public_sdk() { |     async fn test_public_sdk() { | ||||||
|         use pretty_assertions::assert_eq; |         use pretty_assertions::assert_eq; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	
This belongs in the LAMPConfig struct and should use the default option. I think 2G is a sensible default for now.