diff --git a/.gitignore b/.gitignore index 7b1e9f6..f478f21 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ target private_repos log/ *.tgz +.gitignore diff --git a/Cargo.lock b/Cargo.lock index 9481478..e19e6f3 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" @@ -1726,6 +1739,8 @@ name = "harmony" version = "0.1.0" dependencies = [ "async-trait", + "base64 0.22.1", + "bollard", "chrono", "cidr", "convert_case", @@ -1765,6 +1780,7 @@ dependencies = [ "serde_yaml", "similar", "strum 0.27.1", + "tar", "temp-dir", "temp-file", "tempfile", @@ -2728,6 +2744,7 @@ checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638" dependencies = [ "bitflags 2.9.1", "libc", + "redox_syscall", ] [[package]] @@ -4696,6 +4713,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" @@ -5741,6 +5769,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" diff --git a/Cargo.toml b/Cargo.toml index c30f10c..22645f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,3 +53,6 @@ chrono = "0.4" 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/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/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 diff --git a/examples/rust/src/main.rs b/examples/rust/src/main.rs index c44ce88..0b36621 100644 --- a/examples/rust/src/main.rs +++ b/examples/rust/src/main.rs @@ -13,6 +13,12 @@ use harmony::{ #[tokio::main] async fn main() { env_logger::init(); + + let topology = K8sAnywhereTopology::from_env(); + 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()), @@ -24,17 +30,14 @@ async fn main() { features: vec![ Box::new(ContinuousDelivery { application: application.clone(), + }), // TODO add monitoring, backups, multisite ha, etc + Box::new(Monitoring { + application: application.clone(), }), - Box::new(Monitoring {}), - // 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(app)]); harmony_cli::init(maestro, None).await.unwrap(); } diff --git a/harmony/Cargo.toml b/harmony/Cargo.toml index ec2e2fa..87b97ac 100644 --- a/harmony/Cargo.toml +++ b/harmony/Cargo.toml @@ -59,6 +59,9 @@ tokio-util = "0.7.15" strum = { version = "0.27.1", features = ["derive"] } 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/domain/topology/k8s.rs b/harmony/src/domain/topology/k8s.rs index d31e5bc..6ab249a 100644 --- a/harmony/src/domain/topology/k8s.rs +++ b/harmony/src/domain/topology/k8s.rs @@ -260,17 +260,33 @@ impl K8sClient { ) -> 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 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"), + }; // 5. Create a dynamic API client for this resource type. let api: Api = diff --git a/harmony/src/modules/application/features/argo_types.rs b/harmony/src/modules/application/features/argo_types.rs index 197742a..6eba4b7 100644 --- a/harmony/src/modules/application/features/argo_types.rs +++ b/harmony/src/modules/application/features/argo_types.rs @@ -1,11 +1,7 @@ -use std::{backtrace, collections::HashMap}; - -use k8s_openapi::{Metadata, NamespaceResourceScope, Resource}; 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; @@ -33,11 +29,14 @@ 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: Url, + pub repo_url: String, pub target_revision: Option, pub chart: String, pub helm: Helm, + pub path: String, } #[derive(Clone, Debug, Serialize)] @@ -90,7 +89,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 +108,7 @@ impl Default for ArgoApplication { api_versions: vec![], namespace: None, }, + path: "".to_string(), }, sync_policy: SyncPolicy { automated: Automated { @@ -138,10 +138,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 +218,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() @@ -258,7 +258,6 @@ 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, @@ -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 { @@ -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 diff --git a/harmony/src/modules/application/features/continuous_delivery.rs b/harmony/src/modules/application/features/continuous_delivery.rs index 8396124..7bcfb38 100644 --- a/harmony/src/modules/application/features/continuous_delivery.rs +++ b/harmony/src/modules/application/features/continuous_delivery.rs @@ -9,15 +9,12 @@ 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}, + topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology}, }; /// ContinuousDelivery in Harmony provides this functionality : @@ -162,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 @@ -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(), @@ -207,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() @@ -225,7 +215,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..126718d 100644 --- a/harmony/src/modules/application/features/monitoring.rs +++ b/harmony/src/modules/application/features/monitoring.rs @@ -1,36 +1,99 @@ +use std::sync::Arc; + use async_trait::async_trait; -use log::info; +use base64::{Engine as _, engine::general_purpose}; +use log::{debug, info}; use crate::{ inventory::Inventory, modules::{ - application::{Application, ApplicationFeature}, + application::{ApplicationFeature, OCICompliant}, monitoring::{ - application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore, + alert_channel::webhook_receiver::WebhookReceiver, kube_prometheus::{ helm_prometheus_alert_score::HelmPrometheusAlertingScore, 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 {} +#[derive(Debug, Clone)] +pub struct Monitoring { + pub application: Arc, +} #[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, + 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(), + ), + }; + let mut service_monitor = ServiceMonitor::default(); service_monitor.namespace_selector = Some(NamespaceSelector { any: true, match_names: vec![], }); - let alerting_score = ApplicationPrometheusMonitoringScore { - receivers: vec![], + + 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/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 401c01f..62d732b 100644 --- a/harmony/src/modules/application/rust.rs +++ b/harmony/src/modules/application/rust.rs @@ -4,11 +4,15 @@ use std::process; use std::sync::Arc; use async_trait::async_trait; +use bollard::query_parameters::PushImageOptionsBuilder; +use bollard::{Docker, body_full}; use dockerfile_builder::Dockerfile; use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; use dockerfile_builder::instruction_builder::CopyBuilder; +use futures_util::StreamExt; use log::{debug, error, info}; use serde::Serialize; +use tar::Archive; use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; use crate::{ @@ -108,6 +112,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: {}", @@ -117,6 +122,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); @@ -153,66 +159,68 @@ 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()) } /// 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()) } @@ -349,7 +357,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)?; @@ -416,7 +428,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 +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). */}} {{- 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 +449,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 +467,7 @@ kind: Deployment metadata: name: {{ include "chart.fullname" . }} spec: - replicas: {{ .Values.replicaCount }} + replicas: {{ $.Values.replicaCount }} selector: matchLabels: app: {{ include "chart.name" . }} @@ -466,28 +478,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 +508,7 @@ spec: {{- end }} {{- end }} rules: - {{- range .Values.ingress.hosts }} + {{- range $.Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: @@ -507,7 +519,7 @@ spec: service: name: {{ include "chart.fullname" $ }} port: - number: 3000 + number: {{ $.Values.service.port | default 3000 }} {{- end }} {{- end }} {{- end }} @@ -526,11 +538,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")?; @@ -547,7 +563,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. 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?;