use topology domain to build & push helm package for continuous deliery

This commit is contained in:
Ian Letourneau 2025-09-08 21:53:44 -04:00
parent 54803c40a2
commit 3bf5cb0526
11 changed files with 39 additions and 30 deletions

View File

@ -27,7 +27,6 @@ async fn main() {
};
let application = Arc::new(RustWebapp {
name: "example-monitoring".to_string(),
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,

View File

@ -17,7 +17,6 @@ use harmony_types::net::Url;
async fn main() {
let application = Arc::new(RustWebapp {
name: "test-rhob-monitoring".to_string(),
domain: Url::Url(url::Url::parse("htps://some-fake-url").unwrap()),
project_root: PathBuf::from("./webapp"), // Relative from 'harmony-path' param
framework: Some(RustWebFramework::Leptos),
service_port: 3000,

View File

@ -19,7 +19,6 @@ use harmony_macros::hurl;
async fn main() {
let application = Arc::new(RustWebapp {
name: "harmony-example-rust-webapp".to_string(),
domain: hurl!("https://rustapp.harmony.example.com"),
project_root: PathBuf::from("./webapp"),
framework: Some(RustWebFramework::Leptos),
service_port: 3000,

View File

@ -17,7 +17,6 @@ use harmony_types::net::Url;
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,

View File

@ -1,8 +1,7 @@
use crate::topology::{PreparationError, k8s::K8sClient};
use crate::topology::PreparationError;
use async_trait::async_trait;
use std::sync::Arc;
#[async_trait]
pub trait Ingress {
async fn get_domain(&self, client: Arc<K8sClient>) -> Result<String, PreparationError>;
async fn get_domain(&self) -> Result<String, PreparationError>;
}

View File

@ -576,7 +576,9 @@ impl TenantManager for K8sAnywhereTopology {
#[async_trait]
impl Ingress for K8sAnywhereTopology {
//TODO this is specifically for openshift/okd which violates the k8sanywhere idea
async fn get_domain(&self, client: Arc<K8sClient>) -> Result<String, PreparationError> {
async fn get_domain(&self) -> Result<String, PreparationError> {
let client = self.k8s_client().await?;
if let Some(Some(k8s_state)) = self.k8s_state.get() {
match k8s_state.source {
K8sSource::LocalK3d => Ok("localhost".to_string()),

View File

@ -1,4 +1,4 @@
use std::{io::Write, process::Command, sync::Arc};
use std::{io::Write, marker::PhantomData, process::Command, sync::Arc};
use async_trait::async_trait;
use log::info;
@ -10,10 +10,13 @@ use crate::{
data::Version,
inventory::Inventory,
modules::application::{
features::{ArgoApplication, ArgoHelmScore}, ApplicationFeature, HelmPackage, OCICompliant
ApplicationFeature, HelmPackage, OCICompliant,
features::{ArgoApplication, ArgoHelmScore},
},
score::Score,
topology::{ingress::Ingress, DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology},
topology::{
DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, ingress::Ingress,
},
};
/// ContinuousDelivery in Harmony provides this functionality :
@ -140,13 +143,17 @@ impl<
{
async fn ensure_installed(&self, topology: &T) -> Result<(), String> {
let image = self.application.image_name();
let domain_host = topology.get_domain().await.map_err(|e| e.to_string())?;
// TODO Write CI/CD workflow files
// we can autotedect the CI type using the remote url (default to github action for github
// url, etc..)
// Or ask for it when unknown
let helm_chart = self.application.build_push_helm_package(&image).await?;
let helm_chart = self
.application
.build_push_helm_package(&image, &domain_host)
.await?;
// TODO: Make building image configurable/skippable if image already exists (prompt)")
// https://git.nationtech.io/NationTech/harmony/issues/104

View File

@ -55,7 +55,7 @@ impl<T: Topology + K8sclient + HelmCommand + Ingress> Interpret<T> for ArgoInter
topology: &T,
) -> Result<Outcome, InterpretError> {
let k8s_client = topology.k8s_client().await?;
let domain = topology.get_domain(k8s_client.clone()).await?;
let domain = topology.get_domain().await?;
let domain = format!("argo.{domain}");
let helm_score =
argo_helm_chart_score(&self.score.namespace, self.score.openshift, &domain);

View File

@ -1,6 +1,5 @@
use async_trait::async_trait;
use super::Application;
use async_trait::async_trait;
#[async_trait]
pub trait OCICompliant: Application {
@ -17,5 +16,9 @@ pub trait HelmPackage: Application {
///
/// # Arguments
/// * `image_url` - The full URL of the OCI container image to be used in the Deployment.
async fn build_push_helm_package(&self, image_url: &str) -> Result<String, String>;
async fn build_push_helm_package(
&self,
image_url: &str,
domain_host: &str,
) -> Result<String, String>;
}

View File

@ -1,5 +1,4 @@
use std::fs::{self, File};
use std::io::Read;
use std::fs::{self};
use std::path::{Path, PathBuf};
use std::process;
use std::sync::Arc;
@ -13,12 +12,11 @@ use dockerfile_builder::instruction_builder::CopyBuilder;
use futures_util::StreamExt;
use log::{debug, info, log_enabled};
use serde::Serialize;
use tar::{Archive, Builder, Header};
use tar::{Builder, Header};
use walkdir::WalkDir;
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
use crate::{score::Score, topology::Topology};
use harmony_types::net::Url;
use super::{Application, ApplicationFeature, ApplicationInterpret, HelmPackage, OCICompliant};
@ -58,7 +56,6 @@ pub enum RustWebFramework {
#[derive(Debug, Clone, Serialize)]
pub struct RustWebapp {
pub name: String,
pub domain: Url,
/// The path to the root of the Rust project to be containerized.
pub project_root: PathBuf,
pub service_port: u32,
@ -73,12 +70,17 @@ impl Application for RustWebapp {
#[async_trait]
impl HelmPackage for RustWebapp {
async fn build_push_helm_package(&self, image_url: &str) -> Result<String, String> {
async fn build_push_helm_package(
&self,
image_url: &str,
domain_host: &str,
) -> Result<String, String> {
info!("Starting Helm chart build and push for '{}'", self.name);
// 1. Create the Helm chart files on disk.
let chart_dir = self
.create_helm_chart_files(image_url)
.create_helm_chart_files(image_url, domain_host)
.await
.map_err(|e| format!("Failed to create Helm chart files: {}", e))?;
info!("Successfully created Helm chart files in {:?}", chart_dir);
@ -408,10 +410,10 @@ impl RustWebapp {
}
/// Creates all necessary files for a basic Helm chart.
fn create_helm_chart_files(
async fn create_helm_chart_files(
&self,
image_url: &str,
topology: &T,
domain_host: &str,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
let chart_name = format!("{}-chart", self.name);
let chart_dir = self
@ -423,9 +425,9 @@ impl RustWebapp {
fs::create_dir_all(&templates_dir)?;
let (image_repo, image_tag) = image_url.rsplit_once(':').unwrap_or((image_url, "latest"));
//TODO need to find a way to use topology to get the domain
let domain = topology.get_domain(client.clone()).await?;
let domain = format!("{}.{domain_host}", self.name);
// Create Chart.yaml
let chart_yaml = format!(
@ -478,7 +480,7 @@ ingress:
- {}
"#,
chart_name, image_repo, image_tag, self.service_port, domain, self.name
chart_name, image_repo, image_tag, self.service_port, domain, self.name, domain
);
fs::write(chart_dir.join("values.yaml"), values_yaml)?;

View File

@ -275,7 +275,7 @@ impl RHOBAlertingInterpret {
.await
.map_err(|e| InterpretError::new(e.to_string()))?;
let domain = topology.get_domain(client.clone()).await?;
let domain = topology.get_domain().await?;
let name = format!("{}-alert-manager", self.sender.namespace.clone());
let backend_service = format!("{}-alert-manager", self.sender.namespace.clone());
let namespace = self.sender.namespace.clone();
@ -510,7 +510,7 @@ impl RHOBAlertingInterpret {
.apply(&grafana, Some(&self.sender.namespace.clone()))
.await
.map_err(|e| InterpretError::new(e.to_string()))?;
let domain = topology.get_domain(client.clone()).await?;
let domain = topology.get_domain().await?;
let name = format!("{}-grafana", self.sender.namespace.clone());
let backend_service = format!("{}-grafana", self.sender.namespace.clone());
let grafana_ingress = K8sIngressScore {