Compare commits
	
		
			3 Commits
		
	
	
		
			55a4e79ec4
			...
			1525ac2226
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1525ac2226 | |||
|  | 537da5800f | ||
| 3be2fa246c | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -2,3 +2,4 @@ target | |||||||
| private_repos | private_repos | ||||||
| log/ | log/ | ||||||
| *.tgz | *.tgz | ||||||
|  | .gitignore | ||||||
|  | |||||||
							
								
								
									
										38
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										38
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -1355,6 +1355,7 @@ dependencies = [ | |||||||
| name = "example-rust" | name = "example-rust" | ||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  |  "base64 0.22.1", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "harmony", |  "harmony", | ||||||
|  "harmony_cli", |  "harmony_cli", | ||||||
| @ -1427,6 +1428,18 @@ version = "0.2.9" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" | 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]] | [[package]] | ||||||
| name = "flate2" | name = "flate2" | ||||||
| version = "1.1.2" | version = "1.1.2" | ||||||
| @ -1726,6 +1739,8 @@ name = "harmony" | |||||||
| version = "0.1.0" | version = "0.1.0" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "async-trait", |  "async-trait", | ||||||
|  |  "base64 0.22.1", | ||||||
|  |  "bollard", | ||||||
|  "chrono", |  "chrono", | ||||||
|  "cidr", |  "cidr", | ||||||
|  "convert_case", |  "convert_case", | ||||||
| @ -1767,6 +1782,7 @@ dependencies = [ | |||||||
|  "serde_yaml", |  "serde_yaml", | ||||||
|  "similar", |  "similar", | ||||||
|  "strum 0.27.1", |  "strum 0.27.1", | ||||||
|  |  "tar", | ||||||
|  "temp-dir", |  "temp-dir", | ||||||
|  "temp-file", |  "temp-file", | ||||||
|  "tempfile", |  "tempfile", | ||||||
| @ -2746,6 +2762,7 @@ checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bitflags 2.9.1", |  "bitflags 2.9.1", | ||||||
|  "libc", |  "libc", | ||||||
|  |  "redox_syscall", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -4749,6 +4766,17 @@ version = "1.0.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" | 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]] | [[package]] | ||||||
| name = "temp-dir" | name = "temp-dir" | ||||||
| version = "0.1.16" | version = "0.1.16" | ||||||
| @ -5794,6 +5822,16 @@ dependencies = [ | |||||||
|  "tap", |  "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]] | [[package]] | ||||||
| name = "xml-rs" | name = "xml-rs" | ||||||
| version = "0.8.26" | version = "0.8.26" | ||||||
|  | |||||||
| @ -53,3 +53,6 @@ chrono = "0.4" | |||||||
| similar = "2" | similar = "2" | ||||||
| uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] } | uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] } | ||||||
| pretty_assertions = "1.4.1" | pretty_assertions = "1.4.1" | ||||||
|  | bollard = "0.19.1" | ||||||
|  | base64 = "0.22.1" | ||||||
|  | tar = "0.4.44" | ||||||
|  | |||||||
| @ -14,6 +14,7 @@ async fn main() { | |||||||
| 
 | 
 | ||||||
|     maestro.register_all(vec![Box::new(NtfyScore { |     maestro.register_all(vec![Box::new(NtfyScore { | ||||||
|         namespace: "monitoring".to_string(), |         namespace: "monitoring".to_string(), | ||||||
|  |         host: "localhost".to_string(), | ||||||
|     })]); |     })]); | ||||||
|     harmony_cli::init(maestro, None).await.unwrap(); |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -12,3 +12,4 @@ tokio = { workspace = true } | |||||||
| log = { workspace = true } | log = { workspace = true } | ||||||
| env_logger = { workspace = true } | env_logger = { workspace = true } | ||||||
| url = { workspace = true } | url = { workspace = true } | ||||||
|  | base64.workspace = true | ||||||
|  | |||||||
| @ -18,6 +18,12 @@ use harmony::{ | |||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     env_logger::init(); |     env_logger::init(); | ||||||
|  | 
 | ||||||
|  |     let topology = K8sAnywhereTopology::from_env(); | ||||||
|  |     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|     let application = Arc::new(RustWebapp { |     let application = Arc::new(RustWebapp { | ||||||
|         name: "harmony-example-rust-webapp".to_string(), |         name: "harmony-example-rust-webapp".to_string(), | ||||||
|         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), |         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), | ||||||
| @ -39,7 +45,7 @@ async fn main() { | |||||||
|         features: vec![ |         features: vec![ | ||||||
|             Box::new(ContinuousDelivery { |             Box::new(ContinuousDelivery { | ||||||
|                 application: application.clone(), |                 application: application.clone(), | ||||||
|             }), |             }), // TODO add monitoring, backups, multisite ha, etc
 | ||||||
|             Box::new(PrometheusApplicationMonitoring { |             Box::new(PrometheusApplicationMonitoring { | ||||||
|                 application: application.clone(), |                 application: application.clone(), | ||||||
|                 alert_receiver: vec![Box::new(discord_receiver), Box::new(webhook_receiver)], |                 alert_receiver: vec![Box::new(discord_receiver), Box::new(webhook_receiver)], | ||||||
| @ -49,10 +55,6 @@ async fn main() { | |||||||
|         application, |         application, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let topology = K8sAnywhereTopology::from_env(); |  | ||||||
|     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) |  | ||||||
|         .await |  | ||||||
|         .unwrap(); |  | ||||||
|     maestro.register_all(vec![Box::new(app)]); |     maestro.register_all(vec![Box::new(app)]); | ||||||
|     harmony_cli::init(maestro, None).await.unwrap(); |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -61,6 +61,9 @@ tempfile = "3.20.0" | |||||||
| serde_with = "3.14.0" | serde_with = "3.14.0" | ||||||
| schemars = "0.8.22" | schemars = "0.8.22" | ||||||
| kube-derive = "1.1.0" | kube-derive = "1.1.0" | ||||||
|  | bollard.workspace = true | ||||||
|  | tar.workspace = true | ||||||
|  | base64.workspace = true | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| pretty_assertions.workspace = true | pretty_assertions.workspace = true | ||||||
|  | |||||||
| @ -260,17 +260,33 @@ impl K8sClient { | |||||||
|     ) -> Result<(), Error> { |     ) -> Result<(), Error> { | ||||||
|         let obj: DynamicObject = serde_yaml::from_value(yaml.clone()).expect("TODO do not unwrap"); |         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 name = obj.metadata.name.as_ref().expect("YAML must have a name"); | ||||||
|         let namespace = obj |  | ||||||
|             .metadata |  | ||||||
|             .namespace |  | ||||||
|             .as_ref() |  | ||||||
|             .expect("YAML must have a namespace"); |  | ||||||
| 
 | 
 | ||||||
|         // 4. Define the API resource type using the GVK from the object.
 |         let api_version = yaml | ||||||
|         //    The plural name 'applications' is taken from your CRD definition.
 |             .get("apiVersion") | ||||||
|         error!("This only supports argocd application harcoded, very rrrong"); |             .expect("couldn't get apiVersion from YAML") | ||||||
|         let gvk = GroupVersionKind::gvk("argoproj.io", "v1alpha1", "Application"); |             .as_str() | ||||||
|         let api_resource = ApiResource::from_gvk_with_plural(&gvk, "applications"); |             .expect("couldn't get apiVersion as str"); | ||||||
|  |         let kind = yaml | ||||||
|  |             .get("kind") | ||||||
|  |             .expect("couldn't get kind from YAML") | ||||||
|  |             .as_str() | ||||||
|  |             .expect("couldn't get kind as str"); | ||||||
|  | 
 | ||||||
|  |         let split: Vec<&str> = api_version.splitn(2, "/").collect(); | ||||||
|  |         let g = split[0]; | ||||||
|  |         let v = split[1]; | ||||||
|  | 
 | ||||||
|  |         let gvk = GroupVersionKind::gvk(g, v, kind); | ||||||
|  |         let api_resource = ApiResource::from_gvk(&gvk); | ||||||
|  | 
 | ||||||
|  |         let namespace = match ns { | ||||||
|  |             Some(n) => n, | ||||||
|  |             None => obj | ||||||
|  |                 .metadata | ||||||
|  |                 .namespace | ||||||
|  |                 .as_ref() | ||||||
|  |                 .expect("YAML must have a namespace"), | ||||||
|  |         }; | ||||||
| 
 | 
 | ||||||
|         // 5. Create a dynamic API client for this resource type.
 |         // 5. Create a dynamic API client for this resource type.
 | ||||||
|         let api: Api<DynamicObject> = |         let api: Api<DynamicObject> = | ||||||
|  | |||||||
| @ -1,11 +1,7 @@ | |||||||
| use std::{backtrace, collections::HashMap}; |  | ||||||
| 
 |  | ||||||
| use k8s_openapi::{Metadata, NamespaceResourceScope, Resource}; |  | ||||||
| use log::debug; | use log::debug; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use serde_with::skip_serializing_none; | use serde_with::skip_serializing_none; | ||||||
| use serde_yaml::Value; | use serde_yaml::Value; | ||||||
| use url::Url; |  | ||||||
| 
 | 
 | ||||||
| use crate::modules::application::features::CDApplicationConfig; | use crate::modules::application::features::CDApplicationConfig; | ||||||
| 
 | 
 | ||||||
| @ -33,11 +29,14 @@ pub struct Helm { | |||||||
| #[derive(Clone, Debug, Serialize)] | #[derive(Clone, Debug, Serialize)] | ||||||
| #[serde(rename_all = "camelCase")] | #[serde(rename_all = "camelCase")] | ||||||
| pub struct Source { | pub struct Source { | ||||||
|  |     // Using string for this because URL enforces a URL scheme at the beginning but Helm, ArgoCD, etc do not, and it can be counterproductive,
 | ||||||
|  |     // as the only way I've found to get OCI working isn't by using oci:// but rather no scheme at all
 | ||||||
|     #[serde(rename = "repoURL")] |     #[serde(rename = "repoURL")] | ||||||
|     pub repo_url: Url, |     pub repo_url: String, | ||||||
|     pub target_revision: Option<String>, |     pub target_revision: Option<String>, | ||||||
|     pub chart: String, |     pub chart: String, | ||||||
|     pub helm: Helm, |     pub helm: Helm, | ||||||
|  |     pub path: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, Serialize)] | #[derive(Clone, Debug, Serialize)] | ||||||
| @ -90,7 +89,7 @@ impl Default for ArgoApplication { | |||||||
|             namespace: Default::default(), |             namespace: Default::default(), | ||||||
|             project: Default::default(), |             project: Default::default(), | ||||||
|             source: Source { |             source: Source { | ||||||
|                 repo_url: Url::parse("http://asdf").expect("Couldn't parse to URL"), |                 repo_url: "http://asdf".to_string(), | ||||||
|                 target_revision: None, |                 target_revision: None, | ||||||
|                 chart: "".to_string(), |                 chart: "".to_string(), | ||||||
|                 helm: Helm { |                 helm: Helm { | ||||||
| @ -109,6 +108,7 @@ impl Default for ArgoApplication { | |||||||
|                     api_versions: vec![], |                     api_versions: vec![], | ||||||
|                     namespace: None, |                     namespace: None, | ||||||
|                 }, |                 }, | ||||||
|  |                 path: "".to_string(), | ||||||
|             }, |             }, | ||||||
|             sync_policy: SyncPolicy { |             sync_policy: SyncPolicy { | ||||||
|                 automated: Automated { |                 automated: Automated { | ||||||
| @ -138,10 +138,10 @@ impl From<CDApplicationConfig> for ArgoApplication { | |||||||
|             namespace: Some(value.namespace), |             namespace: Some(value.namespace), | ||||||
|             project: "default".to_string(), |             project: "default".to_string(), | ||||||
|             source: Source { |             source: Source { | ||||||
|                 repo_url: Url::parse(value.helm_chart_repo_url.to_string().as_str()) |                 repo_url: value.helm_chart_repo_url, | ||||||
|                     .expect("couldn't convert to URL"), |  | ||||||
|                 target_revision: Some(value.version.to_string()), |                 target_revision: Some(value.version.to_string()), | ||||||
|                 chart: value.helm_chart_name, |                 chart: value.helm_chart_name.clone(), | ||||||
|  |                 path: value.helm_chart_name, | ||||||
|                 helm: Helm { |                 helm: Helm { | ||||||
|                     pass_credentials: None, |                     pass_credentials: None, | ||||||
|                     parameters: vec![], |                     parameters: vec![], | ||||||
| @ -218,7 +218,7 @@ spec: | |||||||
|         let mut yaml_value: Value = |         let mut yaml_value: Value = | ||||||
|             serde_yaml::from_str(yaml_str.as_str()).expect("couldn't parse string to YAML"); |             serde_yaml::from_str(yaml_str.as_str()).expect("couldn't parse string to YAML"); | ||||||
| 
 | 
 | ||||||
|         let mut spec = yaml_value |         let spec = yaml_value | ||||||
|             .get_mut("spec") |             .get_mut("spec") | ||||||
|             .expect("couldn't get spec from yaml") |             .expect("couldn't get spec from yaml") | ||||||
|             .as_mapping_mut() |             .as_mapping_mut() | ||||||
| @ -258,7 +258,6 @@ spec: | |||||||
| #[cfg(test)] | #[cfg(test)] | ||||||
| mod tests { | mod tests { | ||||||
|     use pretty_assertions::assert_eq; |     use pretty_assertions::assert_eq; | ||||||
|     use url::Url; |  | ||||||
| 
 | 
 | ||||||
|     use crate::modules::application::features::{ |     use crate::modules::application::features::{ | ||||||
|         ArgoApplication, Automated, Backoff, Helm, Retry, Source, SyncPolicy, |         ArgoApplication, Automated, Backoff, Helm, Retry, Source, SyncPolicy, | ||||||
| @ -271,7 +270,7 @@ mod tests { | |||||||
|             namespace: Some("test-ns".to_string()), |             namespace: Some("test-ns".to_string()), | ||||||
|             project: "test-project".to_string(), |             project: "test-project".to_string(), | ||||||
|             source: Source { |             source: Source { | ||||||
|                 repo_url: Url::parse("http://test").unwrap(), |                 repo_url: "http://test".to_string(), | ||||||
|                 target_revision: None, |                 target_revision: None, | ||||||
|                 chart: "test-chart".to_string(), |                 chart: "test-chart".to_string(), | ||||||
|                 helm: Helm { |                 helm: Helm { | ||||||
| @ -290,6 +289,7 @@ mod tests { | |||||||
|                     api_versions: vec![], |                     api_versions: vec![], | ||||||
|                     namespace: None, |                     namespace: None, | ||||||
|                 }, |                 }, | ||||||
|  |                 path: "".to_string(), | ||||||
|             }, |             }, | ||||||
|             sync_policy: SyncPolicy { |             sync_policy: SyncPolicy { | ||||||
|                 automated: Automated { |                 automated: Automated { | ||||||
| @ -321,7 +321,7 @@ spec: | |||||||
|     server: https://kubernetes.default.svc
 |     server: https://kubernetes.default.svc
 | ||||||
|     namespace: test-ns |     namespace: test-ns | ||||||
|   source: |   source: | ||||||
|     repoURL: http://test/
 |     repoURL: http://test
 | ||||||
|     chart: test-chart |     chart: test-chart | ||||||
|     helm: |     helm: | ||||||
|       parameters: [] |       parameters: [] | ||||||
| @ -329,6 +329,7 @@ spec: | |||||||
|       releaseName: test-release-neame |       releaseName: test-release-neame | ||||||
|       valueFiles: [] |       valueFiles: [] | ||||||
|       apiVersions: [] |       apiVersions: [] | ||||||
|  |     path: '' | ||||||
|   syncPolicy: |   syncPolicy: | ||||||
|     automated: |     automated: | ||||||
|       prune: false |       prune: false | ||||||
|  | |||||||
| @ -9,15 +9,12 @@ use crate::{ | |||||||
|     config::HARMONY_DATA_DIR, |     config::HARMONY_DATA_DIR, | ||||||
|     data::Version, |     data::Version, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     modules::{ |     modules::application::{ | ||||||
|         application::{ |         Application, ApplicationFeature, HelmPackage, OCICompliant, | ||||||
|             Application, ApplicationFeature, HelmPackage, OCICompliant, |         features::{ArgoApplication, ArgoHelmScore}, | ||||||
|             features::{ArgoApplication, ArgoHelmScore}, |  | ||||||
|         }, |  | ||||||
|         helm::chart::HelmChartScore, |  | ||||||
|     }, |     }, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url}, |     topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /// ContinuousDelivery in Harmony provides this functionality :
 | /// ContinuousDelivery in Harmony provides this functionality :
 | ||||||
| @ -188,12 +185,12 @@ impl< | |||||||
|                 info!("Deploying to target {target:?}"); |                 info!("Deploying to target {target:?}"); | ||||||
|                 let score = ArgoHelmScore { |                 let score = ArgoHelmScore { | ||||||
|                     namespace: "harmonydemo-staging".to_string(), |                     namespace: "harmonydemo-staging".to_string(), | ||||||
|                     openshift: true, |                     openshift: false, | ||||||
|                     domain: "argo.harmonydemo.apps.st.mcd".to_string(), |                     domain: "argo.harmonydemo.apps.st.mcd".to_string(), | ||||||
|                     argo_apps: vec![ArgoApplication::from(CDApplicationConfig { |                     argo_apps: vec![ArgoApplication::from(CDApplicationConfig { | ||||||
|                         // helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart/harmony-example-rust-webapp-chart --version 0.1.0
 |                         // helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart --version 0.1.0
 | ||||||
|                         version: Version::from("0.1.0").unwrap(), |                         version: Version::from("0.1.0").unwrap(), | ||||||
|                         helm_chart_repo_url: Url::Url(url::Url::parse("oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart/harmony-example-rust-webapp-chart").unwrap()), |                         helm_chart_repo_url: "hub.nationtech.io/harmony".to_string(), | ||||||
|                         helm_chart_name: "harmony-example-rust-webapp-chart".to_string(), |                         helm_chart_name: "harmony-example-rust-webapp-chart".to_string(), | ||||||
|                         values_overrides: None, |                         values_overrides: None, | ||||||
|                         name: "harmony-demo-rust-webapp".to_string(), |                         name: "harmony-demo-rust-webapp".to_string(), | ||||||
| @ -207,14 +204,7 @@ impl< | |||||||
|                     .unwrap(); |                     .unwrap(); | ||||||
|             } |             } | ||||||
|         }; |         }; | ||||||
| 
 |         Ok(()) | ||||||
|         todo!("1. Create ArgoCD score that installs argo using helm chart, see if Taha's already done it
 |  | ||||||
|             - [X] Package app (docker image, helm chart) |  | ||||||
|             - [X] Push to registry |  | ||||||
|             - [X] Push only if staging or prod |  | ||||||
|             - [X] Deploy to local k3d when target is local |  | ||||||
|             - [ ] Poke Argo |  | ||||||
|             - [ ] Ensure app is up")
 |  | ||||||
|     } |     } | ||||||
|     fn name(&self) -> String { |     fn name(&self) -> String { | ||||||
|         "ContinuousDelivery".to_string() |         "ContinuousDelivery".to_string() | ||||||
| @ -225,7 +215,7 @@ impl< | |||||||
| /// more CD systems
 | /// more CD systems
 | ||||||
| pub struct CDApplicationConfig { | pub struct CDApplicationConfig { | ||||||
|     pub version: Version, |     pub version: Version, | ||||||
|     pub helm_chart_repo_url: Url, |     pub helm_chart_repo_url: String, | ||||||
|     pub helm_chart_name: String, |     pub helm_chart_name: String, | ||||||
|     pub values_overrides: Option<Value>, |     pub values_overrides: Option<Value>, | ||||||
|     pub name: String, |     pub name: String, | ||||||
|  | |||||||
| @ -1,5 +1,4 @@ | |||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use k8s_openapi::Resource; |  | ||||||
| use log::error; | use log::error; | ||||||
| use non_blank_string_rs::NonBlankString; | use non_blank_string_rs::NonBlankString; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| @ -647,7 +646,7 @@ server: | |||||||
|   # Argo CD server ingress configuration |   # Argo CD server ingress configuration | ||||||
|   ingress: |   ingress: | ||||||
|     # -- Enable an ingress resource for the Argo CD server |     # -- Enable an ingress resource for the Argo CD server | ||||||
|     enabled: false |     enabled: true | ||||||
|     # -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke` |     # -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke` | ||||||
|     ## Additional configuration might be required in related configuration sections |     ## Additional configuration might be required in related configuration sections | ||||||
|     controller: generic |     controller: generic | ||||||
|  | |||||||
| @ -1,27 +1,33 @@ | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::info; | use base64::{Engine as _, engine::general_purpose}; | ||||||
|  | use log::{debug, info}; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     modules::{ |     modules::{ | ||||||
|         application::{Application, ApplicationFeature}, |         application::{Application, ApplicationFeature, OCICompliant}, | ||||||
|         monitoring::kube_prometheus::{ |         monitoring::{ | ||||||
|             alert_manager_config::{CRDAlertManager, CRDAlertManagerReceiver}, |             alert_channel::webhook_receiver::WebhookReceiver, | ||||||
|             helm_prometheus_application_alerting::HelmPrometheusApplicationAlertingScore, |             kube_prometheus::{ | ||||||
|  |                 alert_manager_config::{CRDAlertManager, CRDAlertManagerReceiver}, | ||||||
|  |                 helm_prometheus_application_alerting::HelmPrometheusApplicationAlertingScore, | ||||||
|  |                 types::{NamespaceSelector, ServiceMonitor}, | ||||||
|  |             }, | ||||||
|  |             ntfy::ntfy::NtfyScore, | ||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|     score::Score, |     score::Score, | ||||||
|     topology::{ |     topology::{ | ||||||
|         HelmCommand, K8sclient, Topology, oberservability::monitoring::AlertReceiver, |         HelmCommand, K8sclient, Topology, Url, oberservability::monitoring::AlertReceiver, | ||||||
|         tenant::TenantManager, |         tenant::TenantManager, | ||||||
|     }, |     }, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone)] | #[derive(Debug, Clone)] | ||||||
| pub struct PrometheusApplicationMonitoring { | pub struct PrometheusApplicationMonitoring { | ||||||
|     pub application: Arc<dyn Application>, |     pub application: Arc<dyn OCICompliant>, | ||||||
|     pub alert_receiver: Vec<Box<dyn CRDAlertManagerReceiver>>, |     pub alert_receiver: Vec<Box<dyn CRDAlertManagerReceiver>>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -32,10 +38,66 @@ impl<T: Topology + HelmCommand + 'static + TenantManager + K8sclient + std::fmt: | |||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { |     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { | ||||||
|         info!("Ensuring monitoring is available for application"); |         info!("Ensuring monitoring is available for application"); | ||||||
| 
 | 
 | ||||||
|         let alerting_score = HelmPrometheusApplicationAlertingScore { |         let mut alerting_score = HelmPrometheusApplicationAlertingScore { | ||||||
|             namespace: self.application.name().clone(), |             namespace: self.application.name().clone(), | ||||||
|             receivers: self.alert_receiver.clone(), |             receivers: self.alert_receiver.clone(), | ||||||
|         }; |         }; | ||||||
|  |         let ntfy = NtfyScore { | ||||||
|  |             // namespace: topology
 | ||||||
|  |             //     .get_tenant_config()
 | ||||||
|  |             //     .await
 | ||||||
|  |             //     .expect("couldn't get tenant config")
 | ||||||
|  |             //     .name,
 | ||||||
|  |             namespace: self.application.name(), | ||||||
|  |             host: "localhost".to_string(), | ||||||
|  |         }; | ||||||
|  |         ntfy.create_interpret() | ||||||
|  |             .execute(&Inventory::empty(), topology) | ||||||
|  |             .await | ||||||
|  |             .expect("couldn't create interpret for ntfy"); | ||||||
|  | 
 | ||||||
|  |         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}" | ||||||
|  |             )) | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         debug!("ntfy_default_auth_header: {ntfy_default_auth_header}"); | ||||||
|  | 
 | ||||||
|  |         let ntfy_default_auth_param = general_purpose::STANDARD | ||||||
|  |             .encode(ntfy_default_auth_header) | ||||||
|  |             .replace("=", ""); | ||||||
|  | 
 | ||||||
|  |         debug!("ntfy_default_auth_param: {ntfy_default_auth_param}"); | ||||||
|  | 
 | ||||||
|  |         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}", | ||||||
|  |                         self.application.name() | ||||||
|  |                     ) | ||||||
|  |                     .as_str(), | ||||||
|  |                 ) | ||||||
|  |                 .unwrap(), | ||||||
|  |             ), | ||||||
|  |         }; | ||||||
|  | 
 | ||||||
|  |         alerting_score.receivers.push(Box::new(ntfy_receiver)); | ||||||
|  | 
 | ||||||
|  |         //TODO add service monitors to PrometheusApplicationMonitoring which can be
 | ||||||
|  |         //deployed for the namespace using prometheus crd-servicemonitors
 | ||||||
|  |         let mut service_monitor = ServiceMonitor::default(); | ||||||
|  |         service_monitor.namespace_selector = Some(NamespaceSelector { | ||||||
|  |             any: true, | ||||||
|  |             match_names: vec![], | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         service_monitor.name = "rust-webapp".to_string(); | ||||||
| 
 | 
 | ||||||
|         alerting_score |         alerting_score | ||||||
|             .create_interpret() |             .create_interpret() | ||||||
|  | |||||||
| @ -59,9 +59,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application | |||||||
|                 } |                 } | ||||||
|             }; |             }; | ||||||
|         } |         } | ||||||
|         todo!( |         Ok(Outcome::success("successfully created app".to_string())) | ||||||
|             "Do I need to do anything more than this here?? I feel like the Application trait itself should expose something like ensure_ready but its becoming redundant. We'll see as this evolves." |  | ||||||
|         ) |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|  | |||||||
| @ -4,11 +4,15 @@ use std::process; | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
|  | use bollard::query_parameters::PushImageOptionsBuilder; | ||||||
|  | use bollard::{Docker, body_full}; | ||||||
| use dockerfile_builder::Dockerfile; | use dockerfile_builder::Dockerfile; | ||||||
| use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; | use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; | ||||||
| use dockerfile_builder::instruction_builder::CopyBuilder; | use dockerfile_builder::instruction_builder::CopyBuilder; | ||||||
|  | use futures_util::StreamExt; | ||||||
| use log::{debug, error, info}; | use log::{debug, error, info}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
|  | use tar::Archive; | ||||||
| 
 | 
 | ||||||
| use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; | use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; | ||||||
| use crate::{ | use crate::{ | ||||||
| @ -108,6 +112,7 @@ impl OCICompliant for RustWebapp { | |||||||
|         // 1. Build the local image by calling the synchronous helper function.
 |         // 1. Build the local image by calling the synchronous helper function.
 | ||||||
|         let local_image_name = self.local_image_name(); |         let local_image_name = self.local_image_name(); | ||||||
|         self.build_docker_image(&local_image_name) |         self.build_docker_image(&local_image_name) | ||||||
|  |             .await | ||||||
|             .map_err(|e| format!("Failed to build Docker image: {}", e))?; |             .map_err(|e| format!("Failed to build Docker image: {}", e))?; | ||||||
|         info!( |         info!( | ||||||
|             "Successfully built local Docker image: {}", |             "Successfully built local Docker image: {}", | ||||||
| @ -117,6 +122,7 @@ impl OCICompliant for RustWebapp { | |||||||
|         let remote_image_name = self.image_name(); |         let remote_image_name = self.image_name(); | ||||||
|         // 2. Push the image to the registry.
 |         // 2. Push the image to the registry.
 | ||||||
|         self.push_docker_image(&local_image_name, &remote_image_name) |         self.push_docker_image(&local_image_name, &remote_image_name) | ||||||
|  |             .await | ||||||
|             .map_err(|e| format!("Failed to push Docker image: {}", e))?; |             .map_err(|e| format!("Failed to push Docker image: {}", e))?; | ||||||
|         info!("Successfully pushed Docker image to: {}", remote_image_name); |         info!("Successfully pushed Docker image to: {}", remote_image_name); | ||||||
| 
 | 
 | ||||||
| @ -153,66 +159,68 @@ impl RustWebapp { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Builds the Docker image using the generated Dockerfile.
 |     /// Builds the Docker image using the generated Dockerfile.
 | ||||||
|     pub fn build_docker_image( |     pub async fn build_docker_image( | ||||||
|         &self, |         &self, | ||||||
|         image_name: &str, |         image_name: &str, | ||||||
|     ) -> Result<String, Box<dyn std::error::Error>> { |     ) -> Result<String, Box<dyn std::error::Error>> { | ||||||
|         info!("Generating Dockerfile for '{}'", self.name); |         info!("Generating Dockerfile for '{}'", self.name); | ||||||
|         let dockerfile_path = self.build_dockerfile()?; |         let _dockerfile_path = self.build_dockerfile()?; | ||||||
| 
 | 
 | ||||||
|         info!( |         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||||
|             "Building Docker image with file {} from root {}", | 
 | ||||||
|             dockerfile_path.to_string_lossy(), |         let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default() | ||||||
|             self.project_root.to_string_lossy() |             .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())), | ||||||
|         ); |         ); | ||||||
|         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()?; |  | ||||||
| 
 | 
 | ||||||
|         self.check_output(&output, "Failed to build Docker image")?; |         while let Some(msg) = image_build_stream.next().await { | ||||||
|  |             println!("Message: {msg:?}"); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         Ok(image_name.to_string()) |         Ok(image_name.to_string()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Tags and pushes a Docker image to the configured remote registry.
 |     /// Tags and pushes a Docker image to the configured remote registry.
 | ||||||
|     fn push_docker_image( |     async fn push_docker_image( | ||||||
|         &self, |         &self, | ||||||
|         image_name: &str, |         image_name: &str, | ||||||
|         full_tag: &str, |         full_tag: &str, | ||||||
|     ) -> Result<String, Box<dyn std::error::Error>> { |     ) -> Result<String, Box<dyn std::error::Error>> { | ||||||
|         info!("Pushing docker image {full_tag}"); |         info!("Pushing docker image {full_tag}"); | ||||||
| 
 | 
 | ||||||
|         // Tag the image for the remote registry.
 |         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||||
|         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) |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|         // Push the image.
 |         // let push_options = PushImageOptionsBuilder::new().tag(tag);
 | ||||||
|         let output = process::Command::new("docker") | 
 | ||||||
|             .args(["push", &full_tag]) |         let mut push_image_stream = | ||||||
|             .spawn()? |             docker.push_image(full_tag, Some(PushImageOptionsBuilder::new().build()), None); | ||||||
|             .wait_with_output()?; | 
 | ||||||
|         self.check_output(&output, "Pushing docker image failed")?; |         while let Some(msg) = push_image_stream.next().await { | ||||||
|         debug!( |             println!("Message: {msg:?}"); | ||||||
|             "docker push output: stdout: {}, stderr: {}", |         } | ||||||
|             String::from_utf8_lossy(&output.stdout), |  | ||||||
|             String::from_utf8_lossy(&output.stderr) |  | ||||||
|         ); |  | ||||||
| 
 | 
 | ||||||
|         Ok(full_tag.to_string()) |         Ok(full_tag.to_string()) | ||||||
|     } |     } | ||||||
| @ -349,7 +357,11 @@ impl RustWebapp { | |||||||
|         image_url: &str, |         image_url: &str, | ||||||
|     ) -> Result<PathBuf, Box<dyn std::error::Error>> { |     ) -> Result<PathBuf, Box<dyn std::error::Error>> { | ||||||
|         let chart_name = format!("{}-chart", self.name); |         let chart_name = format!("{}-chart", self.name); | ||||||
|         let chart_dir = self.project_root.join("helm").join(&chart_name); |         let chart_dir = self | ||||||
|  |             .project_root | ||||||
|  |             .join(".harmony_generated") | ||||||
|  |             .join("helm") | ||||||
|  |             .join(&chart_name); | ||||||
|         let templates_dir = chart_dir.join("templates"); |         let templates_dir = chart_dir.join("templates"); | ||||||
|         fs::create_dir_all(&templates_dir)?; |         fs::create_dir_all(&templates_dir)?; | ||||||
| 
 | 
 | ||||||
| @ -416,7 +428,7 @@ ingress: | |||||||
| Expand the name of the chart. | Expand the name of the chart. | ||||||
| */}} | */}} | ||||||
| {{- define "chart.name" -}} | {{- define "chart.name" -}} | ||||||
| {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} | {{- default .Chart.Name $.Values.nameOverride | trunc 63 | trimSuffix "-" }} | ||||||
| {{- end }} | {{- end }} | ||||||
| 
 | 
 | ||||||
| {{/* | {{/* | ||||||
| @ -424,7 +436,7 @@ 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). | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). | ||||||
| */}} | */}} | ||||||
| {{- define "chart.fullname" -}} | {{- define "chart.fullname" -}} | ||||||
| {{- $name := default .Chart.Name .Values.nameOverride }} | {{- $name := default .Chart.Name $.Values.nameOverride }} | ||||||
| {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} | ||||||
| {{- end }} | {{- end }} | ||||||
| "#;
 | "#;
 | ||||||
| @ -437,12 +449,12 @@ kind: Service | |||||||
| metadata: | metadata: | ||||||
|   name: {{ include "chart.fullname" . }} |   name: {{ include "chart.fullname" . }} | ||||||
| spec: | spec: | ||||||
|   type: {{ .Values.service.type }} |   type: {{ $.Values.service.type }} | ||||||
|   ports: |   ports: | ||||||
|     - port: {{ .Values.service.port }} |     - name: main | ||||||
|       targetPort: 3000 |       port: {{ $.Values.service.port | default 3000 }} | ||||||
|  |       targetPort: {{ $.Values.service.port | default 3000 }} | ||||||
|       protocol: TCP |       protocol: TCP | ||||||
|       name: http |  | ||||||
|   selector: |   selector: | ||||||
|     app: {{ include "chart.name" . }} |     app: {{ include "chart.name" . }} | ||||||
| "#;
 | "#;
 | ||||||
| @ -455,7 +467,7 @@ kind: Deployment | |||||||
| metadata: | metadata: | ||||||
|   name: {{ include "chart.fullname" . }} |   name: {{ include "chart.fullname" . }} | ||||||
| spec: | spec: | ||||||
|   replicas: {{ .Values.replicaCount }} |   replicas: {{ $.Values.replicaCount }} | ||||||
|   selector: |   selector: | ||||||
|     matchLabels: |     matchLabels: | ||||||
|       app: {{ include "chart.name" . }} |       app: {{ include "chart.name" . }} | ||||||
| @ -466,28 +478,28 @@ spec: | |||||||
|     spec: |     spec: | ||||||
|       containers: |       containers: | ||||||
|         - name: {{ .Chart.Name }} |         - name: {{ .Chart.Name }} | ||||||
|           image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" |           image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag | default .Chart.AppVersion }}" | ||||||
|           imagePullPolicy: {{ .Values.image.pullPolicy }} |           imagePullPolicy: {{ $.Values.image.pullPolicy }} | ||||||
|           ports: |           ports: | ||||||
|             - name: http |             - name: main | ||||||
|               containerPort: 3000 |               containerPort: {{ $.Values.service.port | default 3000 }} | ||||||
|               protocol: TCP |               protocol: TCP | ||||||
| "#;
 | "#;
 | ||||||
|         fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?; |         fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?; | ||||||
| 
 | 
 | ||||||
|         // Create templates/ingress.yaml
 |         // Create templates/ingress.yaml
 | ||||||
|         let ingress_yaml = r#" |         let ingress_yaml = r#" | ||||||
| {{- if .Values.ingress.enabled -}} | {{- if $.Values.ingress.enabled -}} | ||||||
| apiVersion: networking.k8s.io/v1 | apiVersion: networking.k8s.io/v1 | ||||||
| kind: Ingress | kind: Ingress | ||||||
| metadata: | metadata: | ||||||
|   name: {{ include "chart.fullname" . }} |   name: {{ include "chart.fullname" . }} | ||||||
|   annotations: |   annotations: | ||||||
|     {{- toYaml .Values.ingress.annotations | nindent 4 }} |     {{- toYaml $.Values.ingress.annotations | nindent 4 }} | ||||||
| spec: | spec: | ||||||
|   {{- if .Values.ingress.tls }} |   {{- if $.Values.ingress.tls }} | ||||||
|   tls: |   tls: | ||||||
|     {{- range .Values.ingress.tls }} |     {{- range $.Values.ingress.tls }} | ||||||
|     - hosts: |     - hosts: | ||||||
|         {{- range .hosts }} |         {{- range .hosts }} | ||||||
|         - {{ . | quote }} |         - {{ . | quote }} | ||||||
| @ -496,7 +508,7 @@ spec: | |||||||
|     {{- end }} |     {{- end }} | ||||||
|   {{- end }} |   {{- end }} | ||||||
|   rules: |   rules: | ||||||
|     {{- range .Values.ingress.hosts }} |     {{- range $.Values.ingress.hosts }} | ||||||
|     - host: {{ .host | quote }} |     - host: {{ .host | quote }} | ||||||
|       http: |       http: | ||||||
|         paths: |         paths: | ||||||
| @ -507,7 +519,7 @@ spec: | |||||||
|               service: |               service: | ||||||
|                 name: {{ include "chart.fullname" $ }} |                 name: {{ include "chart.fullname" $ }} | ||||||
|                 port: |                 port: | ||||||
|                   number: 3000 |                   number: {{ $.Values.service.port | default 3000 }} | ||||||
|           {{- end }} |           {{- end }} | ||||||
|     {{- end }} |     {{- end }} | ||||||
| {{- end }} | {{- end }} | ||||||
| @ -526,11 +538,15 @@ spec: | |||||||
|         info!( |         info!( | ||||||
|             "Launching `helm package {}` cli with CWD {}", |             "Launching `helm package {}` cli with CWD {}", | ||||||
|             chart_dirname.to_string_lossy(), |             chart_dirname.to_string_lossy(), | ||||||
|             &self.project_root.join("helm").to_string_lossy() |             &self | ||||||
|  |                 .project_root | ||||||
|  |                 .join(".harmony_generated") | ||||||
|  |                 .join("helm") | ||||||
|  |                 .to_string_lossy() | ||||||
|         ); |         ); | ||||||
|         let output = process::Command::new("helm") |         let output = process::Command::new("helm") | ||||||
|             .args(["package", chart_dirname.to_str().unwrap()]) |             .args(["package", chart_dirname.to_str().unwrap()]) | ||||||
|             .current_dir(&self.project_root.join("helm")) // Run package from the parent dir
 |             .current_dir(&self.project_root.join(".harmony_generated").join("helm")) // Run package from the parent dir
 | ||||||
|             .output()?; |             .output()?; | ||||||
| 
 | 
 | ||||||
|         self.check_output(&output, "Failed to package Helm chart")?; |         self.check_output(&output, "Failed to package Helm chart")?; | ||||||
| @ -547,7 +563,11 @@ spec: | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // The output from helm is relative, so we join it with the execution directory.
 |         // The output from helm is relative, so we join it with the execution directory.
 | ||||||
|         Ok(self.project_root.join("helm").join(tgz_name)) |         Ok(self | ||||||
|  |             .project_root | ||||||
|  |             .join(".harmony_generated") | ||||||
|  |             .join("helm") | ||||||
|  |             .join(tgz_name)) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /// Pushes a packaged Helm chart to an OCI registry.
 |     /// Pushes a packaged Helm chart to an OCI registry.
 | ||||||
|  | |||||||
| @ -24,7 +24,10 @@ pub struct ApplicationPrometheusMonitoringScore { | |||||||
| 
 | 
 | ||||||
| impl<T: Topology + HelmCommand + TenantManager> Score<T> for ApplicationPrometheusMonitoringScore { | impl<T: Topology + HelmCommand + TenantManager> Score<T> for ApplicationPrometheusMonitoringScore { | ||||||
|     fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> { |     fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> { | ||||||
|         let config = Arc::new(Mutex::new(PrometheusConfig::new())); |         let mut prom_config = PrometheusConfig::new(); | ||||||
|  |         prom_config.alert_manager = true; | ||||||
|  | 
 | ||||||
|  |         let config = Arc::new(Mutex::new(prom_config)); | ||||||
|         config |         config | ||||||
|             .try_lock() |             .try_lock() | ||||||
|             .expect("couldn't lock config") |             .expect("couldn't lock config") | ||||||
|  | |||||||
| @ -28,8 +28,6 @@ use super::types::AlertManagerConfig; | |||||||
|     namespaced |     namespaced | ||||||
| )] | )] | ||||||
| pub struct AlertmanagerConfigSpec { | pub struct AlertmanagerConfigSpec { | ||||||
|     // Define the spec fields here, or use serde's `flatten` if you want to store arbitrary data
 |  | ||||||
|     // Example placeholder:
 |  | ||||||
|     #[serde(flatten)] |     #[serde(flatten)] | ||||||
|     pub data: serde_json::Value, |     pub data: serde_json::Value, | ||||||
| } | } | ||||||
|  | |||||||
| @ -28,7 +28,7 @@ impl<T: Topology + HelmCommand + TenantManager> Score<T> for HelmPrometheusAlert | |||||||
|             .expect("couldn't lock config") |             .expect("couldn't lock config") | ||||||
|             .additional_service_monitors = self.service_monitors.clone(); |             .additional_service_monitors = self.service_monitors.clone(); | ||||||
|         Box::new(AlertingInterpret { |         Box::new(AlertingInterpret { | ||||||
|             sender: KubePrometheus::new(), |             sender: KubePrometheus { config }, | ||||||
|             receivers: self.receivers.clone(), |             receivers: self.receivers.clone(), | ||||||
|             rules: self.rules.clone(), |             rules: self.rules.clone(), | ||||||
|         }) |         }) | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ use std::str::FromStr; | |||||||
| 
 | 
 | ||||||
| use crate::modules::helm::chart::{HelmChartScore, HelmRepository}; | use crate::modules::helm::chart::{HelmChartScore, HelmRepository}; | ||||||
| 
 | 
 | ||||||
| pub fn ntfy_helm_chart_score(namespace: String) -> HelmChartScore { | pub fn ntfy_helm_chart_score(namespace: String, host: String) -> HelmChartScore { | ||||||
|     let values = format!( |     let values = format!( | ||||||
|         r#" |         r#" | ||||||
| replicaCount: 1 | replicaCount: 1 | ||||||
| @ -28,12 +28,12 @@ service: | |||||||
|   port: 80 |   port: 80 | ||||||
| 
 | 
 | ||||||
| ingress: | ingress: | ||||||
|   enabled: false |   enabled: true | ||||||
| #  annotations: | #  annotations: | ||||||
|     # kubernetes.io/ingress.class: nginx |     # kubernetes.io/ingress.class: nginx | ||||||
|     # kubernetes.io/tls-acme: "true" |     # kubernetes.io/tls-acme: "true" | ||||||
|   hosts: |   hosts: | ||||||
|     - host: ntfy.host.com |     - host: {host} | ||||||
|       paths: |       paths: | ||||||
|         - path: / |         - path: / | ||||||
|           pathType: ImplementationSpecific |           pathType: ImplementationSpecific | ||||||
|  | |||||||
| @ -17,6 +17,7 @@ use crate::{ | |||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| pub struct NtfyScore { | pub struct NtfyScore { | ||||||
|     pub namespace: String, |     pub namespace: String, | ||||||
|  |     pub host: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + HelmCommand + K8sclient> Score<T> for NtfyScore { | impl<T: Topology + HelmCommand + K8sclient> Score<T> for NtfyScore { | ||||||
| @ -126,7 +127,7 @@ impl<T: Topology + HelmCommand + K8sclient> Interpret<T> for NtfyInterpret { | |||||||
|         inventory: &Inventory, |         inventory: &Inventory, | ||||||
|         topology: &T, |         topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         ntfy_helm_chart_score(self.score.namespace.clone()) |         ntfy_helm_chart_score(self.score.namespace.clone(), self.score.host.clone()) | ||||||
|             .create_interpret() |             .create_interpret() | ||||||
|             .execute(inventory, topology) |             .execute(inventory, topology) | ||||||
|             .await?; |             .await?; | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user