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