Compare commits
	
		
			4 Commits
		
	
	
		
			24401b95a9
			...
			0148d7890d
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 0148d7890d | |||
| ed70bfd236 | |||
| 0a324184ad | |||
| ad2ae2e4f8 | 
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | ||||
| [submodule "examples/try_rust_webapp/tryrust.org"] | ||||
| 	path = examples/try_rust_webapp/tryrust.org | ||||
| 	url = https://github.com/rust-dd/tryrust.org.git | ||||
							
								
								
									
										44
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										44
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1838,6 +1838,21 @@ dependencies = [ | ||||
|  "url", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "example-try-rust-webapp" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "base64 0.22.1", | ||||
|  "env_logger", | ||||
|  "harmony", | ||||
|  "harmony_cli", | ||||
|  "harmony_macros", | ||||
|  "harmony_types", | ||||
|  "log", | ||||
|  "tokio", | ||||
|  "url", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "example-tui" | ||||
| version = "0.1.0" | ||||
| @ -2318,6 +2333,7 @@ dependencies = [ | ||||
|  "tokio-util", | ||||
|  "url", | ||||
|  "uuid", | ||||
|  "walkdir", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -4970,6 +4986,15 @@ dependencies = [ | ||||
|  "cipher", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "same-file" | ||||
| version = "1.0.6" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" | ||||
| dependencies = [ | ||||
|  "winapi-util", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "schannel" | ||||
| version = "0.1.27" | ||||
| @ -6513,6 +6538,16 @@ dependencies = [ | ||||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "walkdir" | ||||
| version = "2.5.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" | ||||
| dependencies = [ | ||||
|  "same-file", | ||||
|  "winapi-util", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "want" | ||||
| version = "0.3.1" | ||||
| @ -6695,6 +6730,15 @@ version = "0.4.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "winapi-util" | ||||
| version = "0.1.10" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22" | ||||
| dependencies = [ | ||||
|  "windows-sys 0.60.2", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "winapi-x86_64-pc-windows-gnu" | ||||
| version = "0.4.0" | ||||
|  | ||||
| @ -30,6 +30,7 @@ async fn main() { | ||||
|         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), | ||||
|         project_root: PathBuf::from("./examples/rust/webapp"), | ||||
|         framework: Some(RustWebFramework::Leptos), | ||||
|         service_port: 3000, | ||||
|     }); | ||||
| 
 | ||||
|     let webhook_receiver = WebhookReceiver { | ||||
|  | ||||
| @ -20,8 +20,9 @@ async fn main() { | ||||
|     let application = Arc::new(RustWebapp { | ||||
|         name: "harmony-example-rust-webapp".to_string(), | ||||
|         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), | ||||
|         project_root: PathBuf::from("./webapp"), // Relative from 'harmony-path' param
 | ||||
|         project_root: PathBuf::from("./webapp"), | ||||
|         framework: Some(RustWebFramework::Leptos), | ||||
|         service_port: 3000, | ||||
|     }); | ||||
| 
 | ||||
|     let discord_receiver = DiscordWebhook { | ||||
|  | ||||
							
								
								
									
										17
									
								
								examples/try_rust_webapp/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								examples/try_rust_webapp/Cargo.toml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| [package] | ||||
| name = "example-try-rust-webapp" | ||||
| edition = "2024" | ||||
| version.workspace = true | ||||
| readme.workspace = true | ||||
| license.workspace = true | ||||
| 
 | ||||
| [dependencies] | ||||
| harmony = { path = "../../harmony" } | ||||
| harmony_cli = { path = "../../harmony_cli" } | ||||
| harmony_types = { path = "../../harmony_types" } | ||||
| harmony_macros = { path = "../../harmony_macros" } | ||||
| tokio = { workspace = true } | ||||
| log = { workspace = true } | ||||
| env_logger = { workspace = true } | ||||
| url = { workspace = true } | ||||
| base64.workspace = true | ||||
							
								
								
									
										52
									
								
								examples/try_rust_webapp/src/main.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								examples/try_rust_webapp/src/main.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| use std::{path::PathBuf, sync::Arc}; | ||||
| 
 | ||||
| use harmony::{ | ||||
|     inventory::Inventory, | ||||
|     modules::{ | ||||
|         application::{ | ||||
|             ApplicationScore, RustWebFramework, RustWebapp, | ||||
|             features::{ContinuousDelivery, Monitoring}, | ||||
|         }, | ||||
|         monitoring::alert_channel::discord_alert_channel::DiscordWebhook, | ||||
|     }, | ||||
|     topology::K8sAnywhereTopology, | ||||
| }; | ||||
| use harmony_types::net::Url; | ||||
| 
 | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
|     let application = Arc::new(RustWebapp { | ||||
|         name: "harmony-example-tryrust".to_string(), | ||||
|         domain: Url::Url(url::Url::parse("https://tryrust.harmony.example.com").unwrap()), | ||||
|         project_root: PathBuf::from("./tryrust.org"), | ||||
|         framework: Some(RustWebFramework::Leptos), | ||||
|         service_port: 8080, | ||||
|     }); | ||||
| 
 | ||||
|     let discord_receiver = DiscordWebhook { | ||||
|         name: "test-discord".to_string(), | ||||
|         url: Url::Url(url::Url::parse("https://discord.doesnt.exist.com").unwrap()), | ||||
|     }; | ||||
| 
 | ||||
|     let app = ApplicationScore { | ||||
|         features: vec![ | ||||
|             Box::new(ContinuousDelivery { | ||||
|                 application: application.clone(), | ||||
|             }), | ||||
|             Box::new(Monitoring { | ||||
|                 application: application.clone(), | ||||
|                 alert_receiver: vec![Box::new(discord_receiver)], | ||||
|             }), | ||||
|         ], | ||||
|         application, | ||||
|     }; | ||||
| 
 | ||||
|     harmony_cli::run( | ||||
|         Inventory::autoload(), | ||||
|         K8sAnywhereTopology::from_env(), | ||||
|         vec![Box::new(app)], | ||||
|         None, | ||||
|     ) | ||||
|     .await | ||||
|     .unwrap(); | ||||
| } | ||||
							
								
								
									
										1
									
								
								examples/try_rust_webapp/tryrust.org
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
								
							
						
						
									
										1
									
								
								examples/try_rust_webapp/tryrust.org
									
									
									
									
									
										Submodule
									
								
							| @ -0,0 +1 @@ | ||||
| Subproject commit 0f9ba145172867f467e5320b37d07a5bbb7dd438 | ||||
| @ -66,6 +66,7 @@ tar.workspace = true | ||||
| base64.workspace = true | ||||
| thiserror.workspace = true | ||||
| once_cell = "1.21.3" | ||||
| walkdir = "2.5.0" | ||||
| harmony_inventory_agent = { path = "../harmony_inventory_agent" } | ||||
| harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" } | ||||
| askama.workspace = true | ||||
|  | ||||
| @ -17,7 +17,7 @@ use kube::{ | ||||
| }; | ||||
| use log::{debug, error, trace}; | ||||
| use serde::{Serialize, de::DeserializeOwned}; | ||||
| use serde_json::json; | ||||
| use serde_json::{Value, json}; | ||||
| use similar::TextDiff; | ||||
| use tokio::io::AsyncReadExt; | ||||
| 
 | ||||
| @ -53,6 +53,21 @@ impl K8sClient { | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn get_resource_json_value( | ||||
|         &self, | ||||
|         name: &str, | ||||
|         namespace: Option<&str>, | ||||
|         gvk: &GroupVersionKind, | ||||
|     ) -> Result<DynamicObject, Error> { | ||||
|         let gvk = ApiResource::from_gvk(gvk); | ||||
|         let resource: Api<DynamicObject> = if let Some(ns) = namespace { | ||||
|             Api::namespaced_with(self.client.clone(), ns, &gvk) | ||||
|         } else { | ||||
|             Api::default_namespaced_with(self.client.clone(), &gvk) | ||||
|         }; | ||||
|         Ok(resource.get(name).await?) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn get_deployment( | ||||
|         &self, | ||||
|         name: &str, | ||||
|  | ||||
| @ -176,18 +176,18 @@ impl< | ||||
|             } | ||||
|             target => { | ||||
|                 info!("Deploying {} to target {target:?}", self.application.name()); | ||||
| 
 | ||||
|                 let score = ArgoHelmScore { | ||||
|                     namespace: "harmony-example-rust-webapp".to_string(), | ||||
|                     namespace: format!("{}", self.application.name()), | ||||
|                     openshift: true, | ||||
|                     domain: "argo.harmonydemo.apps.ncd0.harmony.mcd".to_string(), | ||||
|                     argo_apps: vec![ArgoApplication::from(CDApplicationConfig { | ||||
|                         // helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart --version 0.1.0
 | ||||
|                         version: Version::from("0.1.0").unwrap(), | ||||
|                         helm_chart_repo_url: "hub.nationtech.io/harmony".to_string(), | ||||
|                         helm_chart_name: "harmony-example-rust-webapp-chart".to_string(), | ||||
|                         helm_chart_name: format!("{}-chart", self.application.name()), | ||||
|                         values_overrides: None, | ||||
|                         name: "harmony-demo-rust-webapp".to_string(), | ||||
|                         namespace: "harmony-example-rust-webapp".to_string(), | ||||
|                         name: format!("{}", self.application.name()), | ||||
|                         namespace: format!("{}", self.application.name()), | ||||
|                     })], | ||||
|                 }; | ||||
|                 score | ||||
|  | ||||
| @ -1,7 +1,10 @@ | ||||
| use async_trait::async_trait; | ||||
| use kube::{Api, api::GroupVersionKind}; | ||||
| use log::{debug, warn}; | ||||
| use non_blank_string_rs::NonBlankString; | ||||
| use serde::Serialize; | ||||
| use std::str::FromStr; | ||||
| use serde::de::DeserializeOwned; | ||||
| use std::{process::Command, str::FromStr, sync::Arc}; | ||||
| 
 | ||||
| use crate::{ | ||||
|     data::Version, | ||||
| @ -9,7 +12,9 @@ use crate::{ | ||||
|     inventory::Inventory, | ||||
|     modules::helm::chart::{HelmChartScore, HelmRepository}, | ||||
|     score::Score, | ||||
|     topology::{HelmCommand, K8sclient, Topology}, | ||||
|     topology::{ | ||||
|         HelmCommand, K8sclient, PreparationError, PreparationOutcome, Topology, k8s::K8sClient, | ||||
|     }, | ||||
| }; | ||||
| use harmony_types::id::Id; | ||||
| 
 | ||||
| @ -19,15 +24,13 @@ use super::ArgoApplication; | ||||
| pub struct ArgoHelmScore { | ||||
|     pub namespace: String, | ||||
|     pub openshift: bool, | ||||
|     pub domain: String, | ||||
|     pub argo_apps: Vec<ArgoApplication>, | ||||
| } | ||||
| 
 | ||||
| impl<T: Topology + HelmCommand + K8sclient> Score<T> for ArgoHelmScore { | ||||
|     fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> { | ||||
|         let helm_score = argo_helm_chart_score(&self.namespace, self.openshift, &self.domain); | ||||
|         Box::new(ArgoInterpret { | ||||
|             score: helm_score, | ||||
|             score: self.clone(), | ||||
|             argo_apps: self.argo_apps.clone(), | ||||
|         }) | ||||
|     } | ||||
| @ -39,7 +42,7 @@ impl<T: Topology + HelmCommand + K8sclient> Score<T> for ArgoHelmScore { | ||||
| 
 | ||||
| #[derive(Debug)] | ||||
| pub struct ArgoInterpret { | ||||
|     score: HelmChartScore, | ||||
|     score: ArgoHelmScore, | ||||
|     argo_apps: Vec<ArgoApplication>, | ||||
| } | ||||
| 
 | ||||
| @ -50,9 +53,16 @@ impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for ArgoInterpret { | ||||
|         inventory: &Inventory, | ||||
|         topology: &T, | ||||
|     ) -> Result<Outcome, InterpretError> { | ||||
|         self.score.interpret(inventory, topology).await?; | ||||
| 
 | ||||
|         let k8s_client = topology.k8s_client().await?; | ||||
|         let domain = self | ||||
|             .get_host_domain(k8s_client.clone(), self.score.openshift) | ||||
|             .await?; | ||||
|         let domain = format!("argo.{domain}"); | ||||
|         let helm_score = | ||||
|             argo_helm_chart_score(&self.score.namespace, self.score.openshift, &domain); | ||||
| 
 | ||||
|         helm_score.interpret(inventory, topology).await?; | ||||
| 
 | ||||
|         k8s_client | ||||
|             .apply_yaml_many(&self.argo_apps.iter().map(|a| a.to_yaml()).collect(), None) | ||||
|             .await | ||||
| @ -85,6 +95,38 @@ impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for ArgoInterpret { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl ArgoInterpret { | ||||
|     pub async fn get_host_domain( | ||||
|         &self, | ||||
|         client: Arc<K8sClient>, | ||||
|         openshift: bool, | ||||
|     ) -> Result<String, InterpretError> { | ||||
|         //This should be the job of the topology to determine if we are in
 | ||||
|         //openshift, potentially we need on openshift topology the same way we create a
 | ||||
|         //localhosttopology
 | ||||
|         match openshift { | ||||
|             true => { | ||||
|                 let gvk = GroupVersionKind { | ||||
|                     group: "operator.openshift.io".into(), | ||||
|                     version: "v1".into(), | ||||
|                     kind: "IngressController".into(), | ||||
|                 }; | ||||
|                 let ic = client | ||||
|                     .get_resource_json_value("default", Some("openshift-ingress-operator"), &gvk) | ||||
|                     .await?; | ||||
| 
 | ||||
|                 match ic.data["status"]["domain"].as_str() { | ||||
|                     Some(domain) => return Ok(domain.to_string()), | ||||
|                     None => return Err(InterpretError::new("Could not find domain".to_string())), | ||||
|                 } | ||||
|             } | ||||
|             false => { | ||||
|                 todo!() | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub fn argo_helm_chart_score(namespace: &str, openshift: bool, domain: &str) -> HelmChartScore { | ||||
|     let values = format!( | ||||
|         r#" | ||||
| @ -660,7 +702,7 @@ server: | ||||
|       # nginx.ingress.kubernetes.io/ssl-passthrough: "true" | ||||
| 
 | ||||
|     # -- Defines which ingress controller will implement the resource | ||||
|     ingressClassName: "" | ||||
|     ingressClassName: "openshift-default" | ||||
| 
 | ||||
|     # -- Argo CD server hostname | ||||
|     # @default -- `""` (defaults to global.domain) | ||||
|  | ||||
| @ -1,4 +1,5 @@ | ||||
| use std::fs; | ||||
| use std::fs::{self, File}; | ||||
| use std::io::Read; | ||||
| use std::path::{Path, PathBuf}; | ||||
| use std::process; | ||||
| use std::sync::Arc; | ||||
| @ -12,7 +13,8 @@ use dockerfile_builder::instruction_builder::CopyBuilder; | ||||
| use futures_util::StreamExt; | ||||
| use log::{debug, info, log_enabled}; | ||||
| use serde::Serialize; | ||||
| use tar::Archive; | ||||
| use tar::{Archive, Builder, Header}; | ||||
| use walkdir::WalkDir; | ||||
| 
 | ||||
| use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; | ||||
| use crate::{score::Score, topology::Topology}; | ||||
| @ -59,6 +61,7 @@ pub struct RustWebapp { | ||||
|     pub domain: Url, | ||||
|     /// The path to the root of the Rust project to be containerized.
 | ||||
|     pub project_root: PathBuf, | ||||
|     pub service_port: u32, | ||||
|     pub framework: Option<RustWebFramework>, | ||||
| } | ||||
| 
 | ||||
| @ -158,45 +161,99 @@ impl RustWebapp { | ||||
|         image_name: &str, | ||||
|     ) -> Result<String, Box<dyn std::error::Error>> { | ||||
|         debug!("Generating Dockerfile for '{}'", self.name); | ||||
|         let _dockerfile_path = self.build_dockerfile()?; | ||||
| 
 | ||||
|         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||
| 
 | ||||
|         let dockerfile = self.get_or_build_dockerfile(); | ||||
|         let quiet = !log_enabled!(log::Level::Debug); | ||||
| 
 | ||||
|         let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default() | ||||
|             .dockerfile("Dockerfile.harmony") | ||||
|             .t(image_name) | ||||
|             .q(quiet) | ||||
|             .version(bollard::query_parameters::BuilderVersion::BuilderV1) | ||||
|             .platform("linux/x86_64"); | ||||
| 
 | ||||
|         let mut temp_tar_builder = tar::Builder::new(Vec::new()); | ||||
|         temp_tar_builder | ||||
|             .append_dir_all("", self.project_root.clone()) | ||||
|             .unwrap(); | ||||
|         let archive = temp_tar_builder | ||||
|             .into_inner() | ||||
|             .expect("couldn't finish creating tar"); | ||||
|         let archived_files = Archive::new(archive.as_slice()) | ||||
|             .entries() | ||||
|         match dockerfile | ||||
|             .unwrap() | ||||
|             .map(|entry| entry.unwrap().path().unwrap().into_owned()) | ||||
|             .collect::<Vec<_>>(); | ||||
|             .file_name() | ||||
|             .and_then(|os_str| os_str.to_str()) | ||||
|         { | ||||
|             Some(path_str) => { | ||||
|                 debug!("Building from dockerfile {}", path_str); | ||||
| 
 | ||||
|         debug!("files in docker tar: {:#?}", archived_files); | ||||
|                 let tar_data = self | ||||
|                     .create_deterministic_tar(&self.project_root.clone()) | ||||
|                     .await | ||||
|                     .unwrap(); | ||||
| 
 | ||||
|         let mut image_build_stream = docker.build_image( | ||||
|             build_image_options.build(), | ||||
|             None, | ||||
|             Some(body_full(archive.into())), | ||||
|         ); | ||||
|                 let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||
| 
 | ||||
|         while let Some(msg) = image_build_stream.next().await { | ||||
|             debug!("Message: {msg:?}"); | ||||
|                 let build_image_options = | ||||
|                     bollard::query_parameters::BuildImageOptionsBuilder::default() | ||||
|                         .dockerfile(path_str) | ||||
|                         .t(image_name) | ||||
|                         .q(quiet) | ||||
|                         .version(bollard::query_parameters::BuilderVersion::BuilderV1) | ||||
|                         .platform("linux/x86_64"); | ||||
| 
 | ||||
|                 let mut image_build_stream = docker.build_image( | ||||
|                     build_image_options.build(), | ||||
|                     None, | ||||
|                     Some(body_full(tar_data.into())), | ||||
|                 ); | ||||
| 
 | ||||
|                 while let Some(msg) = image_build_stream.next().await { | ||||
|                     debug!("Message: {msg:?}"); | ||||
|                 } | ||||
| 
 | ||||
|                 Ok(image_name.to_string()) | ||||
|             } | ||||
| 
 | ||||
|             None => Err(Box::new(std::io::Error::new( | ||||
|                 std::io::ErrorKind::InvalidData, | ||||
|                 "Path is not valid UTF-8", | ||||
|             ))), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|         Ok(image_name.to_string()) | ||||
|     ///normalizes timestamp and ignores files that will bust the docker cach
 | ||||
|     async fn create_deterministic_tar( | ||||
|         &self, | ||||
|         project_root: &std::path::Path, | ||||
|     ) -> Result<Vec<u8>, Box<dyn std::error::Error>> { | ||||
|         debug!("building tar file from project root {:#?}", project_root); | ||||
|         let mut tar_data = Vec::new(); | ||||
|         { | ||||
|             let mut builder = Builder::new(&mut tar_data); | ||||
|             let ignore_prefixes = [ | ||||
|                 "target", | ||||
|                 ".git", | ||||
|                 ".github", | ||||
|                 ".harmony_generated", | ||||
|                 "node_modules", | ||||
|             ]; | ||||
|             let mut entries: Vec<_> = WalkDir::new(project_root) | ||||
|                 .into_iter() | ||||
|                 .filter_map(Result::ok) | ||||
|                 .filter(|e| e.file_type().is_file()) | ||||
|                 .filter(|e| { | ||||
|                     let rel_path = e.path().strip_prefix(project_root).unwrap(); | ||||
|                     !ignore_prefixes | ||||
|                         .iter() | ||||
|                         .any(|prefix| rel_path.starts_with(prefix)) | ||||
|                 }) | ||||
|                 .collect(); | ||||
|             entries.sort_by_key(|e| e.path().to_owned()); | ||||
| 
 | ||||
|             for entry in entries { | ||||
|                 let path = entry.path(); | ||||
|                 let rel_path = path.strip_prefix(project_root).unwrap(); | ||||
| 
 | ||||
|                 let mut file = fs::File::open(path)?; | ||||
|                 let mut header = Header::new_gnu(); | ||||
| 
 | ||||
|                 header.set_size(entry.metadata()?.len()); | ||||
|                 header.set_mode(0o644); | ||||
|                 header.set_mtime(0); | ||||
|                 header.set_uid(0); | ||||
|                 header.set_gid(0); | ||||
| 
 | ||||
|                 builder.append_data(&mut header, rel_path, &mut file)?; | ||||
|             } | ||||
| 
 | ||||
|             builder.finish()?; | ||||
|         } | ||||
|         Ok(tar_data) | ||||
|     } | ||||
| 
 | ||||
|     /// Tags and pushes a Docker image to the configured remote registry.
 | ||||
| @ -272,8 +329,11 @@ impl RustWebapp { | ||||
|                     "groupadd -r appgroup && useradd -r -s /bin/false -g appgroup appuser", | ||||
|                 )); | ||||
| 
 | ||||
|                 dockerfile.push(ENV::from("LEPTOS_SITE_ADDR=0.0.0.0:3000")); | ||||
|                 dockerfile.push(EXPOSE::from("3000/tcp")); | ||||
|                 dockerfile.push(ENV::from(format!( | ||||
|                     "LEPTOS_SITE_ADDR=0.0.0.0:{}", | ||||
|                     self.service_port | ||||
|                 ))); | ||||
|                 dockerfile.push(EXPOSE::from(format!("{}/tcp", self.service_port))); | ||||
|                 dockerfile.push(WORKDIR::from("/home/appuser")); | ||||
| 
 | ||||
|                 // Copy static files
 | ||||
| @ -394,7 +454,7 @@ image: | ||||
| 
 | ||||
| service: | ||||
|   type: ClusterIP | ||||
|   port: 3000 | ||||
|   port: {} | ||||
| 
 | ||||
| ingress: | ||||
|   enabled: true | ||||
| @ -414,112 +474,123 @@ ingress: | ||||
|        - chart-example.local | ||||
| 
 | ||||
| "#,
 | ||||
|             chart_name, image_repo, image_tag, self.name | ||||
|             chart_name, image_repo, image_tag, self.service_port, self.name | ||||
|         ); | ||||
|         fs::write(chart_dir.join("values.yaml"), values_yaml)?; | ||||
| 
 | ||||
|         // Create templates/_helpers.tpl
 | ||||
|         let helpers_tpl = r#" | ||||
| {{/* | ||||
|         let helpers_tpl = format!( | ||||
|             r#" | ||||
| {{{{/* | ||||
| Expand the name of the chart. | ||||
| */}} | ||||
| {{- define "chart.name" -}} | ||||
| {{- default .Chart.Name $.Values.nameOverride | trunc 63 | trimSuffix "-" }} | ||||
| {{- end }} | ||||
| */}}}} | ||||
| {{{{- define "chart.name" -}}}} | ||||
| {{{{- default .Chart.Name $.Values.nameOverride | trunc 63 | trimSuffix "-" }}}} | ||||
| {{{{- end }}}} | ||||
| 
 | ||||
| {{/* | ||||
| {{{{/* | ||||
| Create a default fully qualified app name. | ||||
| We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). | ||||
| */}} | ||||
| {{- define "chart.fullname" -}} | ||||
| {{- $name := default .Chart.Name $.Values.nameOverride }} | ||||
| {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} | ||||
| {{- end }} | ||||
| "#;
 | ||||
| */}}}} | ||||
| {{{{- define "chart.fullname" -}}}} | ||||
| {{{{- $name := default .Chart.Name $.Values.nameOverride }}}} | ||||
| {{{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}}} | ||||
| {{{{- end }}}} | ||||
| "#
 | ||||
|         ); | ||||
|         fs::write(templates_dir.join("_helpers.tpl"), helpers_tpl)?; | ||||
| 
 | ||||
|         // Create templates/service.yaml
 | ||||
|         let service_yaml = r#" | ||||
|         let service_yaml = format!( | ||||
|             r#" | ||||
| apiVersion: v1 | ||||
| kind: Service | ||||
| metadata: | ||||
|   name: {{ include "chart.fullname" . }} | ||||
|   name: {{{{ include "chart.fullname" . }}}} | ||||
| spec: | ||||
|   type: {{ $.Values.service.type }} | ||||
|   type: {{{{ $.Values.service.type }}}} | ||||
|   ports: | ||||
|     - name: main | ||||
|       port: {{ $.Values.service.port | default 3000 }} | ||||
|       targetPort: {{ $.Values.service.port | default 3000 }} | ||||
|       port: {{{{ $.Values.service.port | default {} }}}} | ||||
|       targetPort: {{{{ $.Values.service.port | default {} }}}} | ||||
|       protocol: TCP | ||||
|   selector: | ||||
|     app: {{ include "chart.name" . }} | ||||
| "#;
 | ||||
|     app: {{{{ include "chart.name" . }}}} | ||||
| "#,
 | ||||
|             self.service_port, self.service_port | ||||
|         ); | ||||
|         fs::write(templates_dir.join("service.yaml"), service_yaml)?; | ||||
| 
 | ||||
|         // Create templates/deployment.yaml
 | ||||
|         let deployment_yaml = r#" | ||||
|         let deployment_yaml = format!( | ||||
|             r#" | ||||
| apiVersion: apps/v1 | ||||
| kind: Deployment | ||||
| metadata: | ||||
|   name: {{ include "chart.fullname" . }} | ||||
|   name: {{{{ include "chart.fullname" . }}}} | ||||
| spec: | ||||
|   replicas: {{ $.Values.replicaCount }} | ||||
|   replicas: {{{{ $.Values.replicaCount }}}} | ||||
|   selector: | ||||
|     matchLabels: | ||||
|       app: {{ include "chart.name" . }} | ||||
|       app: {{{{ include "chart.name" . }}}} | ||||
|   template: | ||||
|     metadata: | ||||
|       labels: | ||||
|         app: {{ include "chart.name" . }} | ||||
|         app: {{{{ include "chart.name" . }}}} | ||||
|     spec: | ||||
|       containers: | ||||
|         - name: {{ .Chart.Name }} | ||||
|           image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag | default .Chart.AppVersion }}" | ||||
|           imagePullPolicy: {{ $.Values.image.pullPolicy }} | ||||
|         - name: {{{{ .Chart.Name }}}} | ||||
|           image: "{{{{ $.Values.image.repository }}}}:{{{{ $.Values.image.tag | default .Chart.AppVersion }}}}" | ||||
|           imagePullPolicy: {{{{ $.Values.image.pullPolicy }}}} | ||||
|           ports: | ||||
|             - name: main | ||||
|               containerPort: {{ $.Values.service.port | default 3000 }} | ||||
|               containerPort: {{{{ $.Values.service.port | default {} }}}} | ||||
|               protocol: TCP | ||||
| "#;
 | ||||
| "#,
 | ||||
|             self.service_port | ||||
|         ); | ||||
|         fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?; | ||||
| 
 | ||||
|         // Create templates/ingress.yaml
 | ||||
|         let ingress_yaml = r#" | ||||
| {{- if $.Values.ingress.enabled -}} | ||||
|         let ingress_yaml = format!( | ||||
|             r#" | ||||
| {{{{- if $.Values.ingress.enabled -}}}} | ||||
| apiVersion: networking.k8s.io/v1 | ||||
| kind: Ingress | ||||
| metadata: | ||||
|   name: {{ include "chart.fullname" . }} | ||||
|   name: {{{{ include "chart.fullname" . }}}} | ||||
|   annotations: | ||||
|     {{- toYaml $.Values.ingress.annotations | nindent 4 }} | ||||
|     {{{{- toYaml $.Values.ingress.annotations | nindent 4 }}}} | ||||
| spec: | ||||
|   {{- if $.Values.ingress.tls }} | ||||
|   {{{{- if $.Values.ingress.tls }}}} | ||||
|   tls: | ||||
|     {{- range $.Values.ingress.tls }} | ||||
|     {{{{- range $.Values.ingress.tls }}}} | ||||
|     - hosts: | ||||
|         {{- range .hosts }} | ||||
|         - {{ . | quote }} | ||||
|         {{- end }} | ||||
|       secretName: {{ .secretName }} | ||||
|     {{- end }} | ||||
|   {{- end }} | ||||
|         {{{{- range .hosts }}}} | ||||
|         - {{{{ . | quote }}}} | ||||
|         {{{{- end }}}} | ||||
|       secretName: {{{{ .secretName }}}} | ||||
|     {{{{- end }}}} | ||||
|   {{{{- end }}}} | ||||
|   rules: | ||||
|     {{- range $.Values.ingress.hosts }} | ||||
|     - host: {{ .host | quote }} | ||||
|     {{{{- range $.Values.ingress.hosts }}}} | ||||
|     - host: {{{{ .host | quote }}}} | ||||
|       http: | ||||
|         paths: | ||||
|           {{- range .paths }} | ||||
|           - path: {{ .path }} | ||||
|             pathType: {{ .pathType }} | ||||
|           {{{{- range .paths }}}} | ||||
|           - path: {{{{ .path }}}} | ||||
|             pathType: {{{{ .pathType }}}} | ||||
|             backend: | ||||
|               service: | ||||
|                 name: {{ include "chart.fullname" $ }} | ||||
|                 name: {{{{ include "chart.fullname" $ }}}} | ||||
|                 port: | ||||
|                   number: {{ $.Values.service.port | default 3000 }} | ||||
|           {{- end }} | ||||
|     {{- end }} | ||||
| {{- end }} | ||||
| "#;
 | ||||
|                   number: {{{{ $.Values.service.port | default {} }}}} | ||||
|           {{{{- end }}}} | ||||
|     {{{{- end }}}} | ||||
| {{{{- end }}}} | ||||
| "#,
 | ||||
|             self.service_port | ||||
|         ); | ||||
|         fs::write(templates_dir.join("ingress.yaml"), ingress_yaml)?; | ||||
| 
 | ||||
|         Ok(chart_dir) | ||||
| @ -571,7 +642,6 @@ spec: | ||||
|         let chart_file_name = packaged_chart_path.file_stem().unwrap().to_str().unwrap(); | ||||
|         let oci_push_url = format!("oci://{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT); | ||||
|         let oci_pull_url = format!("{oci_push_url}/{}-chart", self.name); | ||||
| 
 | ||||
|         debug!( | ||||
|             "Pushing Helm chart {} to {}", | ||||
|             packaged_chart_path.to_string_lossy(), | ||||
| @ -590,4 +660,20 @@ spec: | ||||
|         debug!("push url {oci_push_url}"); | ||||
|         Ok(format!("{}:{}", oci_pull_url, version)) | ||||
|     } | ||||
| 
 | ||||
|     fn get_or_build_dockerfile(&self) -> Result<PathBuf, Box<dyn std::error::Error>> { | ||||
|         let existing_dockerfile = self.project_root.join("Dockerfile"); | ||||
| 
 | ||||
|         debug!("project_root = {:?}", self.project_root); | ||||
| 
 | ||||
|         debug!("checking = {:?}", existing_dockerfile); | ||||
|         if existing_dockerfile.exists() { | ||||
|             debug!( | ||||
|                 "Checking path {:#?} for existing Dockerfile", | ||||
|                 self.project_root.clone() | ||||
|             ); | ||||
|             return Ok(existing_dockerfile); | ||||
|         } | ||||
|         self.build_dockerfile() | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -197,11 +197,6 @@ impl K8sPrometheusCRDAlertingInterpret { | ||||
|     } | ||||
| 
 | ||||
|     async fn ensure_grafana_operator(&self) -> Result<Outcome, InterpretError> { | ||||
|         if self.crd_exists("grafanas.grafana.integreatly.org").await { | ||||
|             debug!("grafana CRDs already exist — skipping install."); | ||||
|             return Ok(Outcome::success("Grafana CRDs already exist".to_string())); | ||||
|         } | ||||
| 
 | ||||
|         let _ = Command::new("helm") | ||||
|             .args([ | ||||
|                 "repo", | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user