Compare commits
	
		
			No commits in common. "28476af222d76ee28ff36d1aa9eca7a871e44505" and "6de889aa0f7376e663602398686bd86c32005099" have entirely different histories.
		
	
	
		
			28476af222
			...
			6de889aa0f
		
	
		
							
								
								
									
										36
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										36
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1355,7 +1355,6 @@ dependencies = [ | ||||
| name = "example-rust" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "base64 0.22.1", | ||||
|  "env_logger", | ||||
|  "harmony", | ||||
|  "harmony_cli", | ||||
| @ -1428,18 +1427,6 @@ version = "0.2.9" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "filetime" | ||||
| version = "0.2.25" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" | ||||
| dependencies = [ | ||||
|  "cfg-if", | ||||
|  "libc", | ||||
|  "libredox", | ||||
|  "windows-sys 0.59.0", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "flate2" | ||||
| version = "1.1.2" | ||||
| @ -1779,7 +1766,6 @@ dependencies = [ | ||||
|  "serde_yaml", | ||||
|  "similar", | ||||
|  "strum 0.27.1", | ||||
|  "tar", | ||||
|  "temp-dir", | ||||
|  "temp-file", | ||||
|  "tempfile", | ||||
| @ -2743,7 +2729,6 @@ checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" | ||||
| dependencies = [ | ||||
|  "bitflags 2.9.1", | ||||
|  "libc", | ||||
|  "redox_syscall", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| @ -4712,17 +4697,6 @@ version = "1.0.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "tar" | ||||
| version = "0.4.44" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" | ||||
| dependencies = [ | ||||
|  "filetime", | ||||
|  "libc", | ||||
|  "xattr", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "temp-dir" | ||||
| version = "0.1.16" | ||||
| @ -5768,16 +5742,6 @@ dependencies = [ | ||||
|  "tap", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "xattr" | ||||
| version = "1.5.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909" | ||||
| dependencies = [ | ||||
|  "libc", | ||||
|  "rustix 1.0.7", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "xml-rs" | ||||
| version = "0.8.26" | ||||
|  | ||||
| @ -54,5 +54,3 @@ similar = "2" | ||||
| uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] } | ||||
| pretty_assertions = "1.4.1" | ||||
| bollard = "0.19.1" | ||||
| base64 = "0.22.1" | ||||
| tar = "0.4.44" | ||||
|  | ||||
| @ -12,4 +12,3 @@ tokio = { workspace = true } | ||||
| log = { workspace = true } | ||||
| env_logger = { workspace = true } | ||||
| url = { workspace = true } | ||||
| base64.workspace = true | ||||
|  | ||||
| @ -1,6 +1,5 @@ | ||||
| use std::{path::PathBuf, sync::Arc}; | ||||
| 
 | ||||
| use base64::{Engine as _, engine::general_purpose}; | ||||
| use harmony::{ | ||||
|     data::Id, | ||||
|     inventory::Inventory, | ||||
| @ -10,17 +9,11 @@ use harmony::{ | ||||
|             ApplicationScore, RustWebFramework, RustWebapp, | ||||
|             features::{ContinuousDelivery, Monitoring}, | ||||
|         }, | ||||
|         monitoring::{ | ||||
|             alert_channel::webhook_receiver::WebhookReceiver, | ||||
|             kube_prometheus::helm_prometheus_alert_score::HelmPrometheusAlertingScore, | ||||
|             ntfy::ntfy::NtfyScore, | ||||
|         }, | ||||
|         tenant::TenantScore, | ||||
|     }, | ||||
|     score::Score, | ||||
|     topology::{ | ||||
|         K8sAnywhereTopology, Url, | ||||
|         tenant::{ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy}, | ||||
|         tenant::{ResourceLimits, TenantConfig, TenantNetworkPolicy}, | ||||
|     }, | ||||
| }; | ||||
| 
 | ||||
| @ -43,17 +36,6 @@ async fn main() { | ||||
|         }, | ||||
|     }; | ||||
| 
 | ||||
|     let topology = K8sAnywhereTopology::from_env(); | ||||
| 
 | ||||
|     // topology
 | ||||
|     //     .provision_tenant(&tenant.config)
 | ||||
|     //     .await
 | ||||
|     //     .expect("couldn't provision tenant");
 | ||||
| 
 | ||||
|     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) | ||||
|         .await | ||||
|         .unwrap(); | ||||
| 
 | ||||
|     let application = Arc::new(RustWebapp { | ||||
|         name: "harmony-example-rust-webapp".to_string(), | ||||
|         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), | ||||
| @ -61,59 +43,21 @@ async fn main() { | ||||
|         framework: Some(RustWebFramework::Leptos), | ||||
|     }); | ||||
| 
 | ||||
|     let ntfy = NtfyScore { | ||||
|         namespace: tenant.clone().config.name, | ||||
|     }; | ||||
| 
 | ||||
|     let ntfy_default_auth_username = "harmony"; | ||||
|     let ntfy_default_auth_password = "harmony"; | ||||
|     let ntfy_default_auth_header = format!( | ||||
|         "Basic {}", | ||||
|         general_purpose::STANDARD.encode(format!( | ||||
|             "{ntfy_default_auth_username}:{ntfy_default_auth_password}" | ||||
|         )) | ||||
|     ); | ||||
| 
 | ||||
|     let ntfy_default_auth_param = general_purpose::STANDARD | ||||
|         .encode(ntfy_default_auth_header) | ||||
|         .rsplit("=") | ||||
|         .collect::<Vec<&str>>()[0] | ||||
|         .to_string(); | ||||
| 
 | ||||
|     let ntfy_receiver = WebhookReceiver { | ||||
|         name: "ntfy-webhook".to_string(), | ||||
|         url: Url::Url( | ||||
|             url::Url::parse( | ||||
|                 format!( | ||||
|                     "http://ntfy.{}.svc.cluster.local/rust-web-app?auth={ntfy_default_auth_param}", | ||||
|                     tenant.clone().config.name | ||||
|                 ) | ||||
|                 .as_str(), | ||||
|             ) | ||||
|             .unwrap(), | ||||
|         ), | ||||
|     }; | ||||
| 
 | ||||
|     let alerting_score = HelmPrometheusAlertingScore { | ||||
|         receivers: vec![Box::new(ntfy_receiver)], | ||||
|         rules: vec![], | ||||
|         service_monitors: vec![], | ||||
|     }; | ||||
| 
 | ||||
|     let app = ApplicationScore { | ||||
|         features: vec![ | ||||
|             Box::new(ContinuousDelivery { | ||||
|                 application: application.clone(), | ||||
|             }), // TODO add monitoring, backups, multisite ha, etc
 | ||||
|             }), | ||||
|             Box::new(Monitoring {}), | ||||
|             // TODO add monitoring, backups, multisite ha, etc
 | ||||
|         ], | ||||
|         application, | ||||
|     }; | ||||
| 
 | ||||
|     maestro.register_all(vec![ | ||||
|         Box::new(tenant), | ||||
|         Box::new(ntfy), | ||||
|         Box::new(alerting_score), | ||||
|         Box::new(app), | ||||
|     ]); | ||||
|     let topology = K8sAnywhereTopology::from_env(); | ||||
|     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|     maestro.register_all(vec![Box::new(tenant), Box::new(app)]); | ||||
|     harmony_cli::init(maestro, None).await.unwrap(); | ||||
| } | ||||
|  | ||||
| @ -60,7 +60,6 @@ strum = { version = "0.27.1", features = ["derive"] } | ||||
| tempfile = "3.20.0" | ||||
| serde_with = "3.14.0" | ||||
| bollard.workspace = true | ||||
| tar.workspace = true | ||||
| 
 | ||||
| [dev-dependencies] | ||||
| pretty_assertions.workspace = true | ||||
|  | ||||
| @ -2,10 +2,7 @@ use derive_new::new; | ||||
| use futures_util::StreamExt; | ||||
| use k8s_openapi::{ | ||||
|     ClusterResourceScope, NamespaceResourceScope, | ||||
|     api::{ | ||||
|         apps::v1::Deployment, | ||||
|         core::v1::{ObjectReference, Pod}, | ||||
|     }, | ||||
|     api::{apps::v1::Deployment, core::v1::Pod}, | ||||
| }; | ||||
| use kube::{ | ||||
|     Client, Config, Error, Resource, | ||||
| @ -247,39 +244,37 @@ impl K8sClient { | ||||
| 
 | ||||
|     pub async fn apply_yaml_many( | ||||
|         &self, | ||||
|         api_resource: &ApiResource, | ||||
|         yaml: &Vec<serde_yaml::Value>, | ||||
|         ns: Option<&str>, | ||||
|     ) -> Result<(), Error> { | ||||
|         for y in yaml.iter() { | ||||
|             self.apply_yaml(api_resource, y, ns).await?; | ||||
|             self.apply_yaml(y, ns).await?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn apply_yaml( | ||||
|         &self, | ||||
|         api_resource: &ApiResource, | ||||
|         yaml: &serde_yaml::Value, | ||||
|         ns: Option<&str>, | ||||
|     ) -> Result<(), Error> { | ||||
|         let obj: DynamicObject = serde_yaml::from_value(yaml.clone()).expect("TODO do not unwrap"); | ||||
|         let name = obj.metadata.name.as_ref().expect("YAML must have a name"); | ||||
| 
 | ||||
|         let namespace = match ns { | ||||
|             Some(n) => n, | ||||
|             None => { | ||||
|                 obj | ||||
|         let namespace = obj | ||||
|             .metadata | ||||
|             .namespace | ||||
|             .as_ref() | ||||
|                     .expect("YAML must have a namespace") | ||||
|             }, | ||||
|         }; | ||||
|             .expect("YAML must have a namespace"); | ||||
| 
 | ||||
|         // 4. Define the API resource type using the GVK from the object.
 | ||||
|         //    The plural name 'applications' is taken from your CRD definition.
 | ||||
|         error!("This only supports argocd application harcoded, very rrrong"); | ||||
|         let gvk = GroupVersionKind::gvk("argoproj.io", "v1alpha1", "Application"); | ||||
|         let api_resource = ApiResource::from_gvk_with_plural(&gvk, "applications"); | ||||
| 
 | ||||
|         // 5. Create a dynamic API client for this resource type.
 | ||||
|         let api: Api<DynamicObject> = | ||||
|             Api::namespaced_with(self.client.clone(), namespace, api_resource); | ||||
|             Api::namespaced_with(self.client.clone(), namespace, &api_resource); | ||||
| 
 | ||||
|         // 6. Apply the object to the cluster using Server-Side Apply.
 | ||||
|         //    This will create the resource if it doesn't exist, or update it if it does.
 | ||||
|  | ||||
| @ -159,7 +159,7 @@ impl< | ||||
|         info!("Pushed new helm chart {helm_chart}"); | ||||
| 
 | ||||
|         error!("TODO Make building image configurable/skippable"); | ||||
|         let image = self.application.build_push_oci_image().await?; | ||||
|         // let image = self.application.build_push_oci_image().await?;
 | ||||
|         info!("Pushed new docker image {image}"); | ||||
| 
 | ||||
|         info!("Installing ContinuousDelivery feature"); | ||||
|  | ||||
| @ -1,5 +1,4 @@ | ||||
| use async_trait::async_trait; | ||||
| use kube::api::{ApiResource, GroupVersionKind}; | ||||
| use log::error; | ||||
| use non_blank_string_rs::NonBlankString; | ||||
| use serde::Serialize; | ||||
| @ -57,16 +56,9 @@ impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for ArgoInterpret { | ||||
|             .execute(inventory, topology) | ||||
|             .await?; | ||||
| 
 | ||||
|         let gvk = GroupVersionKind::gvk("argoproj.io", "v1alpha1", "Application"); | ||||
|         let api_resource = ApiResource::from_gvk_with_plural(&gvk, "applications"); | ||||
| 
 | ||||
|         let k8s_client = topology.k8s_client().await?; | ||||
|         k8s_client | ||||
|             .apply_yaml_many( | ||||
|                 &api_resource, | ||||
|                 &self.argo_apps.iter().map(|a| a.to_yaml()).collect(), | ||||
|                 None, | ||||
|             ) | ||||
|             .apply_yaml_many(&self.argo_apps.iter().map(|a| a.to_yaml()).collect(), None) | ||||
|             .await | ||||
|             .unwrap(); | ||||
|         Ok(Outcome::success(format!( | ||||
|  | ||||
| @ -1,21 +1,14 @@ | ||||
| use std::fs; | ||||
| use std::io::Read; | ||||
| use std::path::PathBuf; | ||||
| use std::process; | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use async_trait::async_trait; | ||||
| use bollard::image::PushImageOptions; | ||||
| use bollard::query_parameters::PushImageOptionsBuilder; | ||||
| use bollard::{Docker, body_full}; | ||||
| use dockerfile_builder::Dockerfile; | ||||
| use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; | ||||
| use dockerfile_builder::instruction_builder::CopyBuilder; | ||||
| use futures_util::StreamExt; | ||||
| use log::{debug, error, info}; | ||||
| use serde::Serialize; | ||||
| use tar::Archive; | ||||
| use tempfile::tempfile; | ||||
| 
 | ||||
| use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; | ||||
| use crate::{ | ||||
| @ -115,7 +108,6 @@ impl OCICompliant for RustWebapp { | ||||
|         // 1. Build the local image by calling the synchronous helper function.
 | ||||
|         let local_image_name = self.local_image_name(); | ||||
|         self.build_docker_image(&local_image_name) | ||||
|             .await | ||||
|             .map_err(|e| format!("Failed to build Docker image: {}", e))?; | ||||
|         info!( | ||||
|             "Successfully built local Docker image: {}", | ||||
| @ -125,7 +117,6 @@ impl OCICompliant for RustWebapp { | ||||
|         let remote_image_name = self.image_name(); | ||||
|         // 2. Push the image to the registry.
 | ||||
|         self.push_docker_image(&local_image_name, &remote_image_name) | ||||
|             .await | ||||
|             .map_err(|e| format!("Failed to push Docker image: {}", e))?; | ||||
|         info!("Successfully pushed Docker image to: {}", remote_image_name); | ||||
| 
 | ||||
| @ -162,68 +153,66 @@ impl RustWebapp { | ||||
|     } | ||||
| 
 | ||||
|     /// Builds the Docker image using the generated Dockerfile.
 | ||||
|     pub async fn build_docker_image( | ||||
|     pub fn build_docker_image( | ||||
|         &self, | ||||
|         image_name: &str, | ||||
|     ) -> Result<String, Box<dyn std::error::Error>> { | ||||
|         info!("Generating Dockerfile for '{}'", self.name); | ||||
|         let _dockerfile_path = self.build_dockerfile()?; | ||||
|         let dockerfile_path = self.build_dockerfile()?; | ||||
| 
 | ||||
|         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||
| 
 | ||||
|         let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default() | ||||
|             .dockerfile("Dockerfile.harmony") | ||||
|             .t(image_name) | ||||
|             .q(false) | ||||
|             .version(bollard::query_parameters::BuilderVersion::BuilderV1) | ||||
|             .platform("linux/x86_64"); | ||||
| 
 | ||||
|         let mut temp_tar_builder = tar::Builder::new(Vec::new()); | ||||
|         let _ = 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() | ||||
|             .unwrap() | ||||
|             .map(|entry| entry.unwrap().path().unwrap().into_owned()) | ||||
|             .collect::<Vec<_>>(); | ||||
| 
 | ||||
|         debug!("files in docker tar: {:#?}", archived_files); | ||||
| 
 | ||||
|         let mut image_build_stream = docker.build_image( | ||||
|             build_image_options.build(), | ||||
|             None, | ||||
|             Some(body_full(archive.into())), | ||||
|         info!( | ||||
|             "Building Docker image with file {} from root {}", | ||||
|             dockerfile_path.to_string_lossy(), | ||||
|             self.project_root.to_string_lossy() | ||||
|         ); | ||||
|         let output = process::Command::new("docker") | ||||
|             .args([ | ||||
|                 "build", | ||||
|                 "--file", | ||||
|                 dockerfile_path.to_str().unwrap(), | ||||
|                 "-t", | ||||
|                 &image_name, | ||||
|                 self.project_root.to_str().unwrap(), | ||||
|             ]) | ||||
|             .spawn()? | ||||
|             .wait_with_output()?; | ||||
| 
 | ||||
|         while let Some(msg) = image_build_stream.next().await { | ||||
|             println!("Message: {msg:?}"); | ||||
|         } | ||||
|         self.check_output(&output, "Failed to build Docker image")?; | ||||
| 
 | ||||
|         Ok(image_name.to_string()) | ||||
|     } | ||||
| 
 | ||||
|     /// Tags and pushes a Docker image to the configured remote registry.
 | ||||
|     async fn push_docker_image( | ||||
|     fn push_docker_image( | ||||
|         &self, | ||||
|         image_name: &str, | ||||
|         full_tag: &str, | ||||
|     ) -> Result<String, Box<dyn std::error::Error>> { | ||||
|         info!("Pushing docker image {full_tag}"); | ||||
| 
 | ||||
|         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||
|         // Tag the image for the remote registry.
 | ||||
|         let output = process::Command::new("docker") | ||||
|             .args(["tag", image_name, &full_tag]) | ||||
|             .spawn()? | ||||
|             .wait_with_output()?; | ||||
|         self.check_output(&output, "Tagging docker image failed")?; | ||||
|         debug!( | ||||
|             "docker tag output: stdout: {}, stderr: {}", | ||||
|             String::from_utf8_lossy(&output.stdout), | ||||
|             String::from_utf8_lossy(&output.stderr) | ||||
|         ); | ||||
| 
 | ||||
|         // let push_options = PushImageOptionsBuilder::new().tag(tag);
 | ||||
| 
 | ||||
|         let mut push_image_stream = | ||||
|             docker.push_image(full_tag, Some(PushImageOptionsBuilder::new().build()), None); | ||||
| 
 | ||||
|         while let Some(msg) = push_image_stream.next().await { | ||||
|             println!("Message: {msg:?}"); | ||||
|         } | ||||
|         // Push the image.
 | ||||
|         let output = process::Command::new("docker") | ||||
|             .args(["push", &full_tag]) | ||||
|             .spawn()? | ||||
|             .wait_with_output()?; | ||||
|         self.check_output(&output, "Pushing docker image failed")?; | ||||
|         debug!( | ||||
|             "docker push output: stdout: {}, stderr: {}", | ||||
|             String::from_utf8_lossy(&output.stdout), | ||||
|             String::from_utf8_lossy(&output.stderr) | ||||
|         ); | ||||
| 
 | ||||
|         Ok(full_tag.to_string()) | ||||
|     } | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user