From 6de889aa0f7376e663602398686bd86c32005099 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Tue, 8 Jul 2025 01:17:41 -0400 Subject: [PATCH 01/19] Demo now kinda works --- .gitignore | 4 +++ Cargo.lock | 1 + Cargo.toml | 1 + examples/rust/src/main.rs | 33 +++++++++++++++--- harmony/Cargo.toml | 1 + .../application/features/argo_types.rs | 20 +++++------ .../features/continuous_delivery.rs | 19 +++++------ .../application/features/helm_argocd_score.rs | 3 +- .../application/features/monitoring.rs | 7 ++-- harmony/src/modules/application/rust.rs | 34 +++++++++---------- 10 files changed, 73 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 7b1e9f6..f1dcd87 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ target private_repos log/ *.tgz +examples/rust/examples/rust/webapp/helm/ +examples/rust/examples/rust/webapp/Dockerfile.harmony +examples/rust/webapp/helm/harmony-example-rust-webapp-chart/ +.gitignore diff --git a/Cargo.lock b/Cargo.lock index 9481478..3b0347e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1726,6 +1726,7 @@ name = "harmony" version = "0.1.0" dependencies = [ "async-trait", + "bollard", "chrono", "cidr", "convert_case", diff --git a/Cargo.toml b/Cargo.toml index c30f10c..da7f713 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,3 +53,4 @@ chrono = "0.4" similar = "2" uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] } pretty_assertions = "1.4.1" +bollard = "0.19.1" diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index c44ce88..954ca8f 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -1,18 +1,41 @@ use std::{path::PathBuf, sync::Arc}; use harmony::{ + data::Id, inventory::Inventory, maestro::Maestro, - modules::application::{ - ApplicationScore, RustWebFramework, RustWebapp, - features::{ContinuousDelivery, Monitoring}, + modules::{ + application::{ + ApplicationScore, RustWebFramework, RustWebapp, + features::{ContinuousDelivery, Monitoring}, + }, + tenant::TenantScore, + }, + topology::{ + K8sAnywhereTopology, Url, + tenant::{ResourceLimits, TenantConfig, TenantNetworkPolicy}, }, - topology::{K8sAnywhereTopology, Url}, }; #[tokio::main] async fn main() { env_logger::init(); + + let tenant = TenantScore { + config: TenantConfig { + id: Id::from_string("1234".to_string()), + name: "harmonydemo-staging".to_string(), + resource_limits: ResourceLimits { + cpu_request_cores: 6.0, + cpu_limit_cores: 4.0, + memory_request_gb: 4.0, + memory_limit_gb: 4.0, + storage_total_gb: 10.0, + }, + network_policy: TenantNetworkPolicy::default(), + }, + }; + let application = Arc::new(RustWebapp { name: "harmony-example-rust-webapp".to_string(), domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), @@ -35,6 +58,6 @@ async fn main() { let mut maestro = Maestro::initialize(Inventory::autoload(), topology) .await .unwrap(); - maestro.register_all(vec![Box::new(app)]); + maestro.register_all(vec![Box::new(tenant), Box::new(app)]); harmony_cli::init(maestro, None).await.unwrap(); } diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index ec2e2fa..2ffdd04 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -59,6 +59,7 @@ tokio-util = "0.7.15" strum = { version = "0.27.1", features = ["derive"] } tempfile = "3.20.0" serde_with = "3.14.0" +bollard.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/harmony/src/modules/application/features/argo_types.rs b/harmony/src/modules/application/features/argo_types.rs index 197742a..eb42579 100644 --- a/harmony/src/modules/application/features/argo_types.rs +++ b/harmony/src/modules/application/features/argo_types.rs @@ -1,6 +1,3 @@ -use std::{backtrace, collections::HashMap}; - -use k8s_openapi::{Metadata, NamespaceResourceScope, Resource}; use log::debug; use serde::Serialize; use serde_with::skip_serializing_none; @@ -34,10 +31,11 @@ pub struct Helm { #[serde(rename_all = "camelCase")] pub struct Source { #[serde(rename = "repoURL")] - pub repo_url: Url, + pub repo_url: String, pub target_revision: Option, pub chart: String, pub helm: Helm, + pub path: String, } #[derive(Clone, Debug, Serialize)] @@ -90,7 +88,7 @@ impl Default for ArgoApplication { namespace: Default::default(), project: Default::default(), source: Source { - repo_url: Url::parse("http://asdf").expect("Couldn't parse to URL"), + repo_url: "http://asdf".to_string(), target_revision: None, chart: "".to_string(), helm: Helm { @@ -109,6 +107,7 @@ impl Default for ArgoApplication { api_versions: vec![], namespace: None, }, + path: "".to_string(), }, sync_policy: SyncPolicy { automated: Automated { @@ -138,10 +137,10 @@ impl From for ArgoApplication { namespace: Some(value.namespace), project: "default".to_string(), source: Source { - repo_url: Url::parse(value.helm_chart_repo_url.to_string().as_str()) - .expect("couldn't convert to URL"), + repo_url: value.helm_chart_repo_url, 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 { pass_credentials: None, parameters: vec![], @@ -218,7 +217,7 @@ spec: let mut yaml_value: Value = 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") .expect("couldn't get spec from yaml") .as_mapping_mut() @@ -271,7 +270,7 @@ mod tests { namespace: Some("test-ns".to_string()), project: "test-project".to_string(), source: Source { - repo_url: Url::parse("http://test").unwrap(), + repo_url: "http://test".to_string(), target_revision: None, chart: "test-chart".to_string(), helm: Helm { @@ -290,6 +289,7 @@ mod tests { api_versions: vec![], namespace: None, }, + path: "".to_string(), }, sync_policy: SyncPolicy { automated: Automated { diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 8396124..785e169 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -9,12 +9,9 @@ use crate::{ config::HARMONY_DATA_DIR, data::Version, inventory::Inventory, - modules::{ - application::{ - Application, ApplicationFeature, HelmPackage, OCICompliant, - features::{ArgoApplication, ArgoHelmScore}, - }, - helm::chart::HelmChartScore, + modules::application::{ + Application, ApplicationFeature, HelmPackage, OCICompliant, + features::{ArgoApplication, ArgoHelmScore}, }, score::Score, topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url}, @@ -162,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"); @@ -188,12 +185,12 @@ impl< info!("Deploying to target {target:?}"); let score = ArgoHelmScore { namespace: "harmonydemo-staging".to_string(), - openshift: true, + openshift: false, domain: "argo.harmonydemo.apps.st.mcd".to_string(), 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(), - 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(), values_overrides: None, name: "harmony-demo-rust-webapp".to_string(), @@ -225,7 +222,7 @@ impl< /// more CD systems pub struct CDApplicationConfig { pub version: Version, - pub helm_chart_repo_url: Url, + pub helm_chart_repo_url: String, pub helm_chart_name: String, pub values_overrides: Option, pub name: String, diff --git a/harmony/src/modules/application/features/helm_argocd_score.rs b/harmony/src/modules/application/features/helm_argocd_score.rs index d5dbd44..b87b463 100644 --- a/harmony/src/modules/application/features/helm_argocd_score.rs +++ b/harmony/src/modules/application/features/helm_argocd_score.rs @@ -1,5 +1,4 @@ use async_trait::async_trait; -use k8s_openapi::Resource; use log::error; use non_blank_string_rs::NonBlankString; use serde::Serialize; @@ -647,7 +646,7 @@ server: # Argo CD server ingress configuration ingress: # -- Enable an ingress resource for the Argo CD server - enabled: false + enabled: true # -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke` ## Additional configuration might be required in related configuration sections controller: generic diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index 0a8d421..5ca59c0 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -4,13 +4,10 @@ use log::info; use crate::{ inventory::Inventory, modules::{ - application::{Application, ApplicationFeature}, + application::ApplicationFeature, monitoring::{ application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore, - kube_prometheus::{ - helm_prometheus_alert_score::HelmPrometheusAlertingScore, - types::{NamespaceSelector, ServiceMonitor}, - }, + kube_prometheus::types::{NamespaceSelector, ServiceMonitor}, }, }, score::Score, diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index 401c01f..ac684b8 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -416,7 +416,7 @@ ingress: Expand the name of the chart. */}} {{- define "chart.name" -}} -{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- default .Chart.Name $.Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* @@ -424,7 +424,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). */}} {{- define "chart.fullname" -}} -{{- $name := default .Chart.Name .Values.nameOverride }} +{{- $name := default .Chart.Name $.Values.nameOverride }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} "#; @@ -437,12 +437,12 @@ kind: Service metadata: name: {{ include "chart.fullname" . }} spec: - type: {{ .Values.service.type }} + type: {{ $.Values.service.type }} ports: - - port: {{ .Values.service.port }} - targetPort: 3000 + - name: main + port: {{ $.Values.service.port | default 3000 }} + targetPort: {{ $.Values.service.port | default 3000 }} protocol: TCP - name: http selector: app: {{ include "chart.name" . }} "#; @@ -455,7 +455,7 @@ kind: Deployment metadata: name: {{ include "chart.fullname" . }} spec: - replicas: {{ .Values.replicaCount }} + replicas: {{ $.Values.replicaCount }} selector: matchLabels: app: {{ include "chart.name" . }} @@ -466,28 +466,28 @@ spec: spec: containers: - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" - imagePullPolicy: {{ .Values.image.pullPolicy }} + image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ $.Values.image.pullPolicy }} ports: - - name: http - containerPort: 3000 + - name: main + containerPort: {{ $.Values.service.port | default 3000 }} protocol: TCP "#; fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?; // Create templates/ingress.yaml let ingress_yaml = r#" -{{- if .Values.ingress.enabled -}} +{{- if $.Values.ingress.enabled -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: 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 }} @@ -496,7 +496,7 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range $.Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: @@ -507,7 +507,7 @@ spec: service: name: {{ include "chart.fullname" $ }} port: - number: 3000 + number: {{ $.Values.service.port | default 3000 }} {{- end }} {{- end }} {{- end }} -- 2.39.5 From 9fe586532f3fdfb4371fb159f5622f8a2cfafa00 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:12:25 -0400 Subject: [PATCH 02/19] unjank the yaml func --- harmony/src/domain/topology/k8s.rs | 31 +++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index d31e5bc..dcc1307 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -2,7 +2,10 @@ use derive_new::new; use futures_util::StreamExt; use k8s_openapi::{ ClusterResourceScope, NamespaceResourceScope, - api::{apps::v1::Deployment, core::v1::Pod}, + api::{ + apps::v1::Deployment, + core::v1::{ObjectReference, Pod}, + }, }; use kube::{ Client, Config, Error, Resource, @@ -244,37 +247,39 @@ impl K8sClient { pub async fn apply_yaml_many( &self, + api_resource: &ApiResource, yaml: &Vec, ns: Option<&str>, ) -> Result<(), Error> { for y in yaml.iter() { - self.apply_yaml(y, ns).await?; + self.apply_yaml(api_resource, 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 = obj - .metadata - .namespace - .as_ref() - .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"); + 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. let api: Api = - 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. -- 2.39.5 From 753c3eb9d517e07880c32e369442cba87da5fb0d Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:12:39 -0400 Subject: [PATCH 03/19] add monitoring and ntfy --- examples/rust/src/main.rs | 74 ++++++++++++++++++++++++++++++++++----- 1 file changed, 65 insertions(+), 9 deletions(-) diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 954ca8f..e35266a 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -1,5 +1,6 @@ use std::{path::PathBuf, sync::Arc}; +use base64::{Engine as _, engine::general_purpose}; use harmony::{ data::Id, inventory::Inventory, @@ -9,11 +10,17 @@ 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, TenantNetworkPolicy}, + tenant::{ResourceLimits, TenantConfig, TenantManager, TenantNetworkPolicy}, }, }; @@ -36,6 +43,17 @@ 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()), @@ -43,21 +61,59 @@ 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::>()[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(), - }), - Box::new(Monitoring {}), - // TODO add monitoring, backups, multisite ha, etc + }), // TODO add monitoring, backups, multisite ha, etc ], application, }; - 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)]); + maestro.register_all(vec![ + Box::new(tenant), + Box::new(ntfy), + Box::new(alerting_score), + Box::new(app), + ]); harmony_cli::init(maestro, None).await.unwrap(); } -- 2.39.5 From 226fa39f53faa031413140780352493a0e7d130b Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:12:42 -0400 Subject: [PATCH 04/19] add deps --- examples/rust/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/rust/Cargo.toml b/examples/rust/Cargo.toml index 9fcfb2f..2391853 100644 --- a/examples/rust/Cargo.toml +++ b/examples/rust/Cargo.toml @@ -12,3 +12,4 @@ tokio = { workspace = true } log = { workspace = true } env_logger = { workspace = true } url = { workspace = true } +base64.workspace = true -- 2.39.5 From 50870be2d3cfa3c1183ccbf84836379229b97684 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:13:06 -0400 Subject: [PATCH 05/19] add deps --- Cargo.toml | 2 ++ harmony/Cargo.toml | 1 + 2 files changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index da7f713..22645f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,3 +54,5 @@ 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" diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index 2ffdd04..53ad8c7 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -60,6 +60,7 @@ 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.39.5 From cb18ba8e45a9970f89a8a471b8557cbc550c2354 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:13:11 -0400 Subject: [PATCH 06/19] cargo lock --- Cargo.lock | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3b0347e..63df3b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1355,6 +1355,7 @@ dependencies = [ name = "example-rust" version = "0.1.0" dependencies = [ + "base64 0.22.1", "env_logger", "harmony", "harmony_cli", @@ -1427,6 +1428,18 @@ 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" @@ -1766,6 +1779,7 @@ dependencies = [ "serde_yaml", "similar", "strum 0.27.1", + "tar", "temp-dir", "temp-file", "tempfile", @@ -2729,6 +2743,7 @@ checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", + "redox_syscall", ] [[package]] @@ -4697,6 +4712,17 @@ 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" @@ -5742,6 +5768,16 @@ 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" -- 2.39.5 From 0faf85d85072fdd95788fdd11f5e9c3f5ebb1cb6 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:13:27 -0400 Subject: [PATCH 07/19] uncomment docker build --- harmony/src/modules/application/features/continuous_delivery.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 785e169..3ea8042 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -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"); -- 2.39.5 From a9aa989b66e1b33fd664858e3d084b2b4c4c41e7 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:13:42 -0400 Subject: [PATCH 08/19] unjank argo app yaml --- .../modules/application/features/helm_argocd_score.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/harmony/src/modules/application/features/helm_argocd_score.rs b/harmony/src/modules/application/features/helm_argocd_score.rs index b87b463..148d996 100644 --- a/harmony/src/modules/application/features/helm_argocd_score.rs +++ b/harmony/src/modules/application/features/helm_argocd_score.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use kube::api::{ApiResource, GroupVersionKind}; use log::error; use non_blank_string_rs::NonBlankString; use serde::Serialize; @@ -56,9 +57,16 @@ impl Interpret 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(&self.argo_apps.iter().map(|a| a.to_yaml()).collect(), None) + .apply_yaml_many( + &api_resource, + &self.argo_apps.iter().map(|a| a.to_yaml()).collect(), + None, + ) .await .unwrap(); Ok(Outcome::success(format!( -- 2.39.5 From de6969f824cfd58088cd84d8815d9beebd59badd Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:13:51 -0400 Subject: [PATCH 09/19] build using bollard, not CLI --- harmony/src/modules/application/rust.rs | 57 +++++++++++++++++-------- 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index ac684b8..8e1d03b 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -1,14 +1,19 @@ 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::{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::{ @@ -108,6 +113,7 @@ 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: {}", @@ -153,31 +159,46 @@ impl RustWebapp { } /// Builds the Docker image using the generated Dockerfile. - pub fn build_docker_image( + pub async fn build_docker_image( &self, image_name: &str, ) -> Result> { info!("Generating Dockerfile for '{}'", self.name); - let dockerfile_path = self.build_dockerfile()?; + let _dockerfile_path = self.build_dockerfile()?; - info!( - "Building Docker image with file {} from root {}", - dockerfile_path.to_string_lossy(), - self.project_root.to_string_lossy() + 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::>(); + + 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()) } -- 2.39.5 From 28476af222d76ee28ff36d1aa9eca7a871e44505 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 00:24:02 -0400 Subject: [PATCH 10/19] push using bollard --- harmony/src/modules/application/rust.rs | 36 +++++++++---------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index 8e1d03b..f991dc2 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -5,6 +5,8 @@ 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}; @@ -123,6 +125,7 @@ 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); @@ -204,36 +207,23 @@ impl RustWebapp { } /// Tags and pushes a Docker image to the configured remote registry. - fn push_docker_image( + async fn push_docker_image( &self, image_name: &str, full_tag: &str, ) -> Result> { info!("Pushing docker image {full_tag}"); - // 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 docker = Docker::connect_with_socket_defaults().unwrap(); - // 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) - ); + // 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:?}"); + } Ok(full_tag.to_string()) } -- 2.39.5 From 2d8bd5c4aeaa3d4669d4711260de6d91b132d526 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Wed, 9 Jul 2025 23:36:39 -0400 Subject: [PATCH 11/19] Try to get GVK from YAML --- harmony/src/domain/topology/k8s.rs | 36 +++++++++++++------ .../application/features/helm_argocd_score.rs | 9 +---- 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index dcc1307..750dafe 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -247,39 +247,53 @@ impl K8sClient { pub async fn apply_yaml_many( &self, - api_resource: &ApiResource, yaml: &Vec, 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 api_version = yaml + .get("apiVersion") + .expect("couldn't get apiVersion from YAML") + .as_str() + .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") - }, + None => obj + .metadata + .namespace + .as_ref() + .expect("YAML must have a namespace"), }; // 5. Create a dynamic API client for this resource type. let api: Api = - 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. diff --git a/harmony/src/modules/application/features/helm_argocd_score.rs b/harmony/src/modules/application/features/helm_argocd_score.rs index 148d996..bcfa97f 100644 --- a/harmony/src/modules/application/features/helm_argocd_score.rs +++ b/harmony/src/modules/application/features/helm_argocd_score.rs @@ -57,16 +57,9 @@ impl Interpret 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!( -- 2.39.5 From d7de1ea752568108970f8315688a6fd2f7707882 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Thu, 10 Jul 2025 01:51:18 -0400 Subject: [PATCH 12/19] Move ntfy into monitoring feature --- .gitignore | 3 -- Cargo.lock | 1 + examples/rust/src/main.rs | 53 +------------------ harmony/Cargo.toml | 1 + .../features/continuous_delivery.rs | 9 +--- .../application/features/monitoring.rs | 53 +++++++++++++++++-- harmony/src/modules/application/mod.rs | 4 +- harmony/src/modules/application/rust.rs | 20 +++++-- 8 files changed, 72 insertions(+), 72 deletions(-) diff --git a/.gitignore b/.gitignore index f1dcd87..f478f21 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,4 @@ target private_repos log/ *.tgz -examples/rust/examples/rust/webapp/helm/ -examples/rust/examples/rust/webapp/Dockerfile.harmony -examples/rust/webapp/helm/harmony-example-rust-webapp-chart/ .gitignore diff --git a/Cargo.lock b/Cargo.lock index 63df3b3..e19e6f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1739,6 +1739,7 @@ name = "harmony" version = "0.1.0" dependencies = [ "async-trait", + "base64 0.22.1", "bollard", "chrono", "cidr", diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index e35266a..d7b8fce 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -44,12 +44,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(); @@ -61,59 +55,16 @@ 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::>()[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 {}), ], application, }; - maestro.register_all(vec![ - Box::new(tenant), - Box::new(ntfy), - Box::new(alerting_score), - Box::new(app), - ]); + maestro.register_all(vec![Box::new(app)]); harmony_cli::init(maestro, None).await.unwrap(); } diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index 53ad8c7..87b97ac 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -61,6 +61,7 @@ tempfile = "3.20.0" serde_with = "3.14.0" bollard.workspace = true tar.workspace = true +base64.workspace = true [dev-dependencies] pretty_assertions.workspace = true diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 3ea8042..447b08e 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -204,14 +204,7 @@ impl< .unwrap(); } }; - - 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") + Ok(()) } fn name(&self) -> String { "ContinuousDelivery".to_string() diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index 5ca59c0..c89fa38 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -1,4 +1,5 @@ use async_trait::async_trait; +use base64::{Engine as _, engine::general_purpose}; use log::info; use crate::{ @@ -6,28 +7,74 @@ use crate::{ modules::{ application::ApplicationFeature, monitoring::{ + alert_channel::webhook_receiver::WebhookReceiver, application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore, kube_prometheus::types::{NamespaceSelector, ServiceMonitor}, + ntfy::ntfy::NtfyScore, }, }, score::Score, - topology::{HelmCommand, Topology, tenant::TenantManager}, + topology::{HelmCommand, K8sclient, Topology, Url, tenant::TenantManager}, }; #[derive(Debug, Default, Clone)] pub struct Monitoring {} #[async_trait] -impl ApplicationFeature for Monitoring { +impl ApplicationFeature + for Monitoring +{ async fn ensure_installed(&self, topology: &T) -> Result<(), String> { info!("Ensuring monitoring is available for application"); + + let ntfy = NtfyScore { + namespace: topology + .get_tenant_config() + .await + .expect("couldn't get tenant config") + .name, + }; + 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}" + )) + ); + + let ntfy_default_auth_param = general_purpose::STANDARD + .encode(ntfy_default_auth_header) + .rsplit("=") + .collect::>()[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}", + topology.get_tenant_config().await.expect("couldn't get tenant config").name + ) + .as_str(), + ) + .unwrap(), + ), + }; + let mut service_monitor = ServiceMonitor::default(); service_monitor.namespace_selector = Some(NamespaceSelector { any: true, match_names: vec![], }); let alerting_score = ApplicationPrometheusMonitoringScore { - receivers: vec![], + receivers: vec![Box::new(ntfy_receiver)], rules: vec![], service_monitors: vec![service_monitor], }; diff --git a/harmony/src/modules/application/mod.rs b/harmony/src/modules/application/mod.rs index afdb88c..3788217 100644 --- a/harmony/src/modules/application/mod.rs +++ b/harmony/src/modules/application/mod.rs @@ -59,9 +59,7 @@ impl Interpret for Application } }; } - todo!( - "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." - ) + Ok(Outcome::success("successfully created app".to_string())) } fn get_name(&self) -> InterpretName { diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index f991dc2..3db36d1 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -360,7 +360,11 @@ impl RustWebapp { image_url: &str, ) -> Result> { 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"); fs::create_dir_all(&templates_dir)?; @@ -537,11 +541,15 @@ spec: info!( "Launching `helm package {}` cli with CWD {}", 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") .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()?; self.check_output(&output, "Failed to package Helm chart")?; @@ -558,7 +566,11 @@ spec: } // 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. -- 2.39.5 From cc4529617c69e71bade4faebeb02e4d9db9bdef3 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Thu, 10 Jul 2025 11:24:45 -0400 Subject: [PATCH 13/19] forgot to push --- examples/rust/src/main.rs | 9 +-------- harmony/src/domain/topology/k8s.rs | 2 +- harmony/src/modules/application/features/argo_types.rs | 3 +-- .../modules/application/features/continuous_delivery.rs | 2 +- .../modules/application/features/helm_argocd_score.rs | 1 - harmony/src/modules/application/rust.rs | 3 --- 6 files changed, 4 insertions(+), 16 deletions(-) diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index d7b8fce..5195805 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -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}, }, }; diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index 750dafe..c2896ca 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -4,7 +4,7 @@ use k8s_openapi::{ ClusterResourceScope, NamespaceResourceScope, api::{ apps::v1::Deployment, - core::v1::{ObjectReference, Pod}, + core::v1::Pod, }, }; use kube::{ diff --git a/harmony/src/modules/application/features/argo_types.rs b/harmony/src/modules/application/features/argo_types.rs index eb42579..15bb896 100644 --- a/harmony/src/modules/application/features/argo_types.rs +++ b/harmony/src/modules/application/features/argo_types.rs @@ -2,7 +2,6 @@ use log::debug; use serde::Serialize; use serde_with::skip_serializing_none; use serde_yaml::Value; -use url::Url; use crate::modules::application::features::CDApplicationConfig; @@ -257,7 +256,7 @@ spec: #[cfg(test)] mod tests { use pretty_assertions::assert_eq; - use url::Url; + use crate::modules::application::features::{ ArgoApplication, Automated, Backoff, Helm, Retry, Source, SyncPolicy, diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 447b08e..39513ab 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -14,7 +14,7 @@ use crate::{ features::{ArgoApplication, ArgoHelmScore}, }, score::Score, - topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url}, + topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology}, }; /// ContinuousDelivery in Harmony provides this functionality : diff --git a/harmony/src/modules/application/features/helm_argocd_score.rs b/harmony/src/modules/application/features/helm_argocd_score.rs index bcfa97f..b87b463 100644 --- a/harmony/src/modules/application/features/helm_argocd_score.rs +++ b/harmony/src/modules/application/features/helm_argocd_score.rs @@ -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; diff --git a/harmony/src/modules/application/rust.rs b/harmony/src/modules/application/rust.rs index 3db36d1..62d732b 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -1,11 +1,9 @@ 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; @@ -15,7 +13,6 @@ 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::{ -- 2.39.5 From 81bee7e12ad4b41645a4e87b738be34b03b76b21 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Thu, 10 Jul 2025 11:27:40 -0400 Subject: [PATCH 14/19] remove tenant score --- examples/rust/src/main.rs | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 5195805..3df4775 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -21,21 +21,6 @@ use harmony::{ async fn main() { env_logger::init(); - let tenant = TenantScore { - config: TenantConfig { - id: Id::from_string("1234".to_string()), - name: "harmonydemo-staging".to_string(), - resource_limits: ResourceLimits { - cpu_request_cores: 6.0, - cpu_limit_cores: 4.0, - memory_request_gb: 4.0, - memory_limit_gb: 4.0, - storage_total_gb: 10.0, - }, - network_policy: TenantNetworkPolicy::default(), - }, - }; - let topology = K8sAnywhereTopology::from_env(); let mut maestro = Maestro::initialize(Inventory::autoload(), topology) .await -- 2.39.5 From d0d80aee28632cfc068ffe578b308d795282d63a Mon Sep 17 00:00:00 2001 From: tahahawa Date: Thu, 10 Jul 2025 13:26:12 -0400 Subject: [PATCH 15/19] worked with will to get monitoring + ntfy demoed/tested --- examples/ntfy/src/main.rs | 1 + .../features/continuous_delivery.rs | 4 +- .../application/features/monitoring.rs | 40 +++++++++++++------ .../k8s_application_monitoring_score.rs | 5 ++- .../monitoring/kube_prometheus/helm/config.rs | 12 +++--- .../helm/kube_prometheus_helm_chart.rs | 40 +++++++++---------- .../helm_prometheus_alert_score.rs | 2 +- .../monitoring/ntfy/helm/ntfy_helm_chart.rs | 6 +-- harmony/src/modules/monitoring/ntfy/ntfy.rs | 3 +- 9 files changed, 67 insertions(+), 46 deletions(-) diff --git a/examples/ntfy/src/main.rs b/examples/ntfy/src/main.rs index f359a61..fc04e6e 100644 --- a/examples/ntfy/src/main.rs +++ b/examples/ntfy/src/main.rs @@ -14,6 +14,7 @@ async fn main() { maestro.register_all(vec![Box::new(NtfyScore { namespace: "monitoring".to_string(), + host: "localhost".to_string(), })]); harmony_cli::init(maestro, None).await.unwrap(); } diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 39513ab..7bcfb38 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -159,8 +159,8 @@ impl< info!("Pushed new helm chart {helm_chart}"); error!("TODO Make building image configurable/skippable"); - let image = self.application.build_push_oci_image().await?; - info!("Pushed new docker image {image}"); + // let image = self.application.build_push_oci_image().await?; + // info!("Pushed new docker image {image}"); info!("Installing ContinuousDelivery feature"); // TODO this is a temporary hack for demo purposes, the deployment target should be driven diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index c89fa38..61429ea 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use base64::{Engine as _, engine::general_purpose}; -use log::info; +use log::{debug, info}; use crate::{ inventory::Inventory, @@ -9,7 +9,10 @@ use crate::{ monitoring::{ alert_channel::webhook_receiver::WebhookReceiver, application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore, - kube_prometheus::types::{NamespaceSelector, ServiceMonitor}, + kube_prometheus::{ + helm_prometheus_alert_score::HelmPrometheusAlertingScore, + types::{NamespaceSelector, ServiceMonitor}, + }, ntfy::ntfy::NtfyScore, }, }, @@ -28,11 +31,13 @@ impl Applicatio info!("Ensuring monitoring is available for application"); let ntfy = NtfyScore { - namespace: topology - .get_tenant_config() - .await - .expect("couldn't get tenant config") - .name, + // namespace: topology + // .get_tenant_config() + // .await + // .expect("couldn't get tenant config") + // .name, + namespace: "harmonydemo-staging".to_string(), + host: "localhost".to_string(), }; ntfy.create_interpret() .execute(&Inventory::empty(), topology) @@ -48,11 +53,13 @@ impl Applicatio )) ); + debug!("ntfy_default_auth_header: {ntfy_default_auth_header}"); + let ntfy_default_auth_param = general_purpose::STANDARD .encode(ntfy_default_auth_header) - .rsplit("=") - .collect::>()[0] - .to_string(); + .replace("=", ""); + + debug!("ntfy_default_auth_param: {ntfy_default_auth_param}"); let ntfy_receiver = WebhookReceiver { name: "ntfy-webhook".to_string(), @@ -60,7 +67,7 @@ impl Applicatio url::Url::parse( format!( "http://ntfy.{}.svc.cluster.local/rust-web-app?auth={ntfy_default_auth_param}", - topology.get_tenant_config().await.expect("couldn't get tenant config").name + "harmonydemo-staging".to_string() ) .as_str(), ) @@ -73,7 +80,16 @@ impl Applicatio any: true, match_names: vec![], }); - let alerting_score = ApplicationPrometheusMonitoringScore { + + service_monitor.name = "rust-webapp".to_string(); + + // let alerting_score = ApplicationPrometheusMonitoringScore { + // receivers: vec![Box::new(ntfy_receiver)], + // rules: vec![], + // service_monitors: vec![service_monitor], + // }; + + let alerting_score = HelmPrometheusAlertingScore { receivers: vec![Box::new(ntfy_receiver)], rules: vec![], service_monitors: vec![service_monitor], diff --git a/harmony/src/modules/monitoring/application_monitoring/k8s_application_monitoring_score.rs b/harmony/src/modules/monitoring/application_monitoring/k8s_application_monitoring_score.rs index 29e2893..f4a6c1b 100644 --- a/harmony/src/modules/monitoring/application_monitoring/k8s_application_monitoring_score.rs +++ b/harmony/src/modules/monitoring/application_monitoring/k8s_application_monitoring_score.rs @@ -24,7 +24,10 @@ pub struct ApplicationPrometheusMonitoringScore { impl Score for ApplicationPrometheusMonitoringScore { fn create_interpret(&self) -> Box> { - 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 .try_lock() .expect("couldn't lock config") diff --git a/harmony/src/modules/monitoring/kube_prometheus/helm/config.rs b/harmony/src/modules/monitoring/kube_prometheus/helm/config.rs index 3f273c6..041e5f0 100644 --- a/harmony/src/modules/monitoring/kube_prometheus/helm/config.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/helm/config.rs @@ -38,15 +38,15 @@ impl KubePrometheusConfig { node_exporter: false, prometheus: true, kubernetes_service_monitors: true, - kubernetes_api_server: false, + kubernetes_api_server: true, kubelet: true, - kube_controller_manager: false, - kube_etcd: false, - kube_proxy: false, + kube_controller_manager: true, + kube_etcd: true, + kube_proxy: true, kube_state_metrics: true, prometheus_operator: true, - core_dns: false, - kube_scheduler: false, + core_dns: true, + kube_scheduler: true, alert_receiver_configs: vec![], alert_rules: vec![], additional_service_monitors: vec![], diff --git a/harmony/src/modules/monitoring/kube_prometheus/helm/kube_prometheus_helm_chart.rs b/harmony/src/modules/monitoring/kube_prometheus/helm/kube_prometheus_helm_chart.rs index 14d9f5f..22b2f7a 100644 --- a/harmony/src/modules/monitoring/kube_prometheus/helm/kube_prometheus_helm_chart.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/helm/kube_prometheus_helm_chart.rs @@ -70,12 +70,12 @@ pub fn kube_prometheus_helm_chart_score( r#" global: rbac: - create: false + create: true prometheus: enabled: {prometheus} prometheusSpec: resources: - requests: + requests: cpu: 100m memory: 500Mi limits: @@ -121,7 +121,7 @@ defaultRules: windowsMonitoring: enabled: {windows_monitoring} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -130,13 +130,13 @@ windowsMonitoring: grafana: enabled: {grafana} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: cpu: 200m memory: 250Mi - initChownData: + initChownData: resources: requests: cpu: 10m @@ -157,7 +157,7 @@ kubernetesServiceMonitors: kubeApiServer: enabled: {kubernetes_api_server} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -166,7 +166,7 @@ kubeApiServer: kubelet: enabled: {kubelet} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -175,7 +175,7 @@ kubelet: kubeControllerManager: enabled: {kube_controller_manager} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -184,7 +184,7 @@ kubeControllerManager: coreDns: enabled: {core_dns} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -193,7 +193,7 @@ coreDns: kubeEtcd: enabled: {kube_etcd} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -202,7 +202,7 @@ kubeEtcd: kubeScheduler: enabled: {kube_scheduler} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -211,7 +211,7 @@ kubeScheduler: kubeProxy: enabled: {kube_proxy} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -221,7 +221,7 @@ kubeStateMetrics: enabled: {kube_state_metrics} kube-state-metrics: resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -230,7 +230,7 @@ kube-state-metrics: nodeExporter: enabled: {node_exporter} resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -238,16 +238,16 @@ nodeExporter: memory: 250Mi prometheus-node-exporter: resources: - requests: + requests: cpu: 100m memory: 150Mi limits: cpu: 200m memory: 250Mi prometheusOperator: - enabled: false + enabled: true resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -255,7 +255,7 @@ prometheusOperator: memory: 200Mi prometheusConfigReloader: resources: - requests: + requests: cpu: 100m memory: 150Mi limits: @@ -267,7 +267,7 @@ prometheusOperator: limits: cpu: 10m memory: 100Mi - requests: + requests: cpu: 10m memory: 100Mi patch: @@ -275,7 +275,7 @@ prometheusOperator: limits: cpu: 10m memory: 100Mi - requests: + requests: cpu: 10m memory: 100Mi "#, diff --git a/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs b/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs index 0ab5f1b..c9a0c04 100644 --- a/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs +++ b/harmony/src/modules/monitoring/kube_prometheus/helm_prometheus_alert_score.rs @@ -28,7 +28,7 @@ impl Score for HelmPrometheusAlert .expect("couldn't lock config") .additional_service_monitors = self.service_monitors.clone(); Box::new(AlertingInterpret { - sender: KubePrometheus::new(), + sender: KubePrometheus { config }, receivers: self.receivers.clone(), rules: self.rules.clone(), }) diff --git a/harmony/src/modules/monitoring/ntfy/helm/ntfy_helm_chart.rs b/harmony/src/modules/monitoring/ntfy/helm/ntfy_helm_chart.rs index db7d9c4..076a8a3 100644 --- a/harmony/src/modules/monitoring/ntfy/helm/ntfy_helm_chart.rs +++ b/harmony/src/modules/monitoring/ntfy/helm/ntfy_helm_chart.rs @@ -3,7 +3,7 @@ use std::str::FromStr; 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!( r#" replicaCount: 1 @@ -28,12 +28,12 @@ service: port: 80 ingress: - enabled: false + enabled: true # annotations: # kubernetes.io/ingress.class: nginx # kubernetes.io/tls-acme: "true" hosts: - - host: ntfy.host.com + - host: {host} paths: - path: / pathType: ImplementationSpecific diff --git a/harmony/src/modules/monitoring/ntfy/ntfy.rs b/harmony/src/modules/monitoring/ntfy/ntfy.rs index 98d4fff..773b0ad 100644 --- a/harmony/src/modules/monitoring/ntfy/ntfy.rs +++ b/harmony/src/modules/monitoring/ntfy/ntfy.rs @@ -17,6 +17,7 @@ use crate::{ #[derive(Debug, Clone, Serialize)] pub struct NtfyScore { pub namespace: String, + pub host: String, } impl Score for NtfyScore { @@ -126,7 +127,7 @@ impl Interpret for NtfyInterpret { inventory: &Inventory, topology: &T, ) -> Result { - ntfy_helm_chart_score(self.score.namespace.clone()) + ntfy_helm_chart_score(self.score.namespace.clone(), self.score.host.clone()) .create_interpret() .execute(inventory, topology) .await?; -- 2.39.5 From c573e7f64c9080c2b9a182fb8217a2049096a8fb Mon Sep 17 00:00:00 2001 From: tahahawa Date: Thu, 10 Jul 2025 13:30:15 -0400 Subject: [PATCH 16/19] cargo fmt/fix --- examples/rust/src/main.rs | 7 +------ harmony/src/domain/topology/k8s.rs | 5 +---- harmony/src/modules/application/features/argo_types.rs | 3 ++- harmony/src/modules/application/features/monitoring.rs | 1 - 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 3df4775..6df2ce9 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -1,19 +1,14 @@ use std::{path::PathBuf, sync::Arc}; use harmony::{ - data::Id, inventory::Inventory, maestro::Maestro, - modules::{ - application::{ + modules::application::{ ApplicationScore, RustWebFramework, RustWebapp, features::{ContinuousDelivery, Monitoring}, }, - tenant::TenantScore, - }, topology::{ K8sAnywhereTopology, Url, - tenant::{ResourceLimits, TenantConfig, TenantNetworkPolicy}, }, }; diff --git a/harmony/src/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index c2896ca..6ab249a 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -2,10 +2,7 @@ use derive_new::new; use futures_util::StreamExt; use k8s_openapi::{ ClusterResourceScope, NamespaceResourceScope, - api::{ - apps::v1::Deployment, - core::v1::Pod, - }, + api::{apps::v1::Deployment, core::v1::Pod}, }; use kube::{ Client, Config, Error, Resource, diff --git a/harmony/src/modules/application/features/argo_types.rs b/harmony/src/modules/application/features/argo_types.rs index 15bb896..45b429a 100644 --- a/harmony/src/modules/application/features/argo_types.rs +++ b/harmony/src/modules/application/features/argo_types.rs @@ -29,6 +29,8 @@ pub struct Helm { #[derive(Clone, Debug, Serialize)] #[serde(rename_all = "camelCase")] 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")] pub repo_url: String, pub target_revision: Option, @@ -256,7 +258,6 @@ spec: #[cfg(test)] mod tests { use pretty_assertions::assert_eq; - use crate::modules::application::features::{ ArgoApplication, Automated, Backoff, Helm, Retry, Source, SyncPolicy, diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index 61429ea..fa7ac71 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -8,7 +8,6 @@ use crate::{ application::ApplicationFeature, monitoring::{ alert_channel::webhook_receiver::WebhookReceiver, - application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore, kube_prometheus::{ helm_prometheus_alert_score::HelmPrometheusAlertingScore, types::{NamespaceSelector, ServiceMonitor}, -- 2.39.5 From 694f0dc04581b9d44b1971c1d74b7b24e47d5d0f Mon Sep 17 00:00:00 2001 From: tahahawa Date: Fri, 11 Jul 2025 09:41:18 -0400 Subject: [PATCH 17/19] fix app name/namespace --- examples/rust/src/main.rs | 8 +++----- .../src/modules/application/features/monitoring.rs | 14 +++++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 6df2ce9..92bcbd0 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -4,12 +4,10 @@ use harmony::{ inventory::Inventory, maestro::Maestro, modules::application::{ - ApplicationScore, RustWebFramework, RustWebapp, - features::{ContinuousDelivery, Monitoring}, - }, - topology::{ - K8sAnywhereTopology, Url, + ApplicationScore, RustWebFramework, RustWebapp, + features::{ContinuousDelivery, Monitoring}, }, + topology::{K8sAnywhereTopology, Url}, }; #[tokio::main] diff --git a/harmony/src/modules/application/features/monitoring.rs b/harmony/src/modules/application/features/monitoring.rs index fa7ac71..126718d 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use async_trait::async_trait; use base64::{Engine as _, engine::general_purpose}; use log::{debug, info}; @@ -5,7 +7,7 @@ use log::{debug, info}; use crate::{ inventory::Inventory, modules::{ - application::ApplicationFeature, + application::{ApplicationFeature, OCICompliant}, monitoring::{ alert_channel::webhook_receiver::WebhookReceiver, kube_prometheus::{ @@ -19,8 +21,10 @@ use crate::{ topology::{HelmCommand, K8sclient, Topology, Url, tenant::TenantManager}, }; -#[derive(Debug, Default, Clone)] -pub struct Monitoring {} +#[derive(Debug, Clone)] +pub struct Monitoring { + pub application: Arc, +} #[async_trait] impl ApplicationFeature @@ -35,7 +39,7 @@ impl Applicatio // .await // .expect("couldn't get tenant config") // .name, - namespace: "harmonydemo-staging".to_string(), + namespace: self.application.name(), host: "localhost".to_string(), }; ntfy.create_interpret() @@ -66,7 +70,7 @@ impl Applicatio url::Url::parse( format!( "http://ntfy.{}.svc.cluster.local/rust-web-app?auth={ntfy_default_auth_param}", - "harmonydemo-staging".to_string() + self.application.name() ) .as_str(), ) -- 2.39.5 From b97c58bc3418799b0bba7a813eae04e825d51c77 Mon Sep 17 00:00:00 2001 From: tahahawa Date: Fri, 11 Jul 2025 10:12:25 -0400 Subject: [PATCH 18/19] fix example --- examples/rust/src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index 92bcbd0..0b36621 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -31,7 +31,9 @@ async fn main() { Box::new(ContinuousDelivery { application: application.clone(), }), // TODO add monitoring, backups, multisite ha, etc - Box::new(Monitoring {}), + Box::new(Monitoring { + application: application.clone(), + }), ], application, }; -- 2.39.5 From 17ebdc124734ed1a6b76d759486a42542016a6dd Mon Sep 17 00:00:00 2001 From: tahahawa Date: Fri, 11 Jul 2025 10:23:19 -0400 Subject: [PATCH 19/19] update test --- harmony/src/modules/application/features/argo_types.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/harmony/src/modules/application/features/argo_types.rs b/harmony/src/modules/application/features/argo_types.rs index 45b429a..6eba4b7 100644 --- a/harmony/src/modules/application/features/argo_types.rs +++ b/harmony/src/modules/application/features/argo_types.rs @@ -321,7 +321,7 @@ spec: server: https://kubernetes.default.svc namespace: test-ns source: - repoURL: http://test/ + repoURL: http://test chart: test-chart helm: parameters: [] @@ -329,6 +329,7 @@ spec: releaseName: test-release-neame valueFiles: [] apiVersions: [] + path: '' syncPolicy: automated: prune: false -- 2.39.5