fix: unjank the demo #85

Merged
taha merged 19 commits from fix_demo into master 2025-07-11 14:32:22 +00:00
10 changed files with 73 additions and 50 deletions
Showing only changes of commit 6de889aa0f - Show all commits

4
.gitignore vendored
View File

@ -2,3 +2,7 @@ target
private_repos private_repos
log/ log/
*.tgz *.tgz
examples/rust/examples/rust/webapp/helm/
Review

These would be better in the examples's own gitignore file examples/rust/.gitignore. Eventually we may have hundreds of examples, we don't want thousands of lines in this file.

But thinking a bit further, this points towards a problem on the way we manage these generated files. They should probably end up in a harmony specific directory that we can then ignore in a single line for all examples. Maybe harmony_generated or harmony_build ?

These would be better in the examples's own gitignore file `examples/rust/.gitignore`. Eventually we may have hundreds of examples, we don't want thousands of lines in this file. But thinking a bit further, this points towards a problem on the way we manage these generated files. They should probably end up in a harmony specific directory that we can then ignore in a single line for all examples. Maybe `harmony_generated` or `harmony_build` ?
examples/rust/examples/rust/webapp/Dockerfile.harmony
examples/rust/webapp/helm/harmony-example-rust-webapp-chart/
.gitignore

1
Cargo.lock generated
View File

@ -1726,6 +1726,7 @@ name = "harmony"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bollard",
"chrono", "chrono",
"cidr", "cidr",
"convert_case", "convert_case",

View File

@ -53,3 +53,4 @@ chrono = "0.4"
similar = "2" similar = "2"
uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] } uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] }
pretty_assertions = "1.4.1" pretty_assertions = "1.4.1"
bollard = "0.19.1"

View File

@ -1,18 +1,41 @@
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
use harmony::{ use harmony::{
data::Id,
inventory::Inventory, inventory::Inventory,
maestro::Maestro, maestro::Maestro,
modules::application::{ modules::{
ApplicationScore, RustWebFramework, RustWebapp, application::{
features::{ContinuousDelivery, Monitoring}, ApplicationScore, RustWebFramework, RustWebapp,
features::{ContinuousDelivery, Monitoring},
},
tenant::TenantScore,
},
topology::{
K8sAnywhereTopology, Url,
tenant::{ResourceLimits, TenantConfig, TenantNetworkPolicy},
}, },
topology::{K8sAnywhereTopology, Url},
}; };
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
env_logger::init(); 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 { let application = Arc::new(RustWebapp {
name: "harmony-example-rust-webapp".to_string(), name: "harmony-example-rust-webapp".to_string(),
domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()),
@ -35,6 +58,6 @@ async fn main() {
let mut maestro = Maestro::initialize(Inventory::autoload(), topology) let mut maestro = Maestro::initialize(Inventory::autoload(), topology)
.await .await
.unwrap(); .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(); harmony_cli::init(maestro, None).await.unwrap();
} }

View File

@ -59,6 +59,7 @@ tokio-util = "0.7.15"
strum = { version = "0.27.1", features = ["derive"] } strum = { version = "0.27.1", features = ["derive"] }
tempfile = "3.20.0" tempfile = "3.20.0"
serde_with = "3.14.0" serde_with = "3.14.0"
bollard.workspace = true
[dev-dependencies] [dev-dependencies]
pretty_assertions.workspace = true pretty_assertions.workspace = true

View File

@ -1,6 +1,3 @@
use std::{backtrace, collections::HashMap};
use k8s_openapi::{Metadata, NamespaceResourceScope, Resource};
use log::debug; use log::debug;
use serde::Serialize; use serde::Serialize;
use serde_with::skip_serializing_none; use serde_with::skip_serializing_none;
@ -34,10 +31,11 @@ pub struct Helm {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Source { pub struct Source {
#[serde(rename = "repoURL")] #[serde(rename = "repoURL")]
pub repo_url: Url, pub repo_url: String,
pub target_revision: Option<String>, pub target_revision: Option<String>,
pub chart: String, pub chart: String,
pub helm: Helm, pub helm: Helm,
pub path: String,
} }
#[derive(Clone, Debug, Serialize)] #[derive(Clone, Debug, Serialize)]
@ -90,7 +88,7 @@ impl Default for ArgoApplication {
namespace: Default::default(), namespace: Default::default(),
project: Default::default(), project: Default::default(),
source: Source { source: Source {
repo_url: Url::parse("http://asdf").expect("Couldn't parse to URL"), repo_url: "http://asdf".to_string(),
target_revision: None, target_revision: None,
chart: "".to_string(), chart: "".to_string(),
helm: Helm { helm: Helm {
@ -109,6 +107,7 @@ impl Default for ArgoApplication {
api_versions: vec![], api_versions: vec![],
namespace: None, namespace: None,
}, },
path: "".to_string(),
}, },
sync_policy: SyncPolicy { sync_policy: SyncPolicy {
automated: Automated { automated: Automated {
@ -138,10 +137,10 @@ impl From<CDApplicationConfig> for ArgoApplication {
namespace: Some(value.namespace), namespace: Some(value.namespace),
project: "default".to_string(), project: "default".to_string(),
source: Source { source: Source {
repo_url: Url::parse(value.helm_chart_repo_url.to_string().as_str()) repo_url: value.helm_chart_repo_url,
.expect("couldn't convert to URL"),
target_revision: Some(value.version.to_string()), target_revision: Some(value.version.to_string()),
chart: value.helm_chart_name, chart: value.helm_chart_name.clone(),
path: value.helm_chart_name,
helm: Helm { helm: Helm {
pass_credentials: None, pass_credentials: None,
parameters: vec![], parameters: vec![],
@ -218,7 +217,7 @@ spec:
let mut yaml_value: Value = let mut yaml_value: Value =
serde_yaml::from_str(yaml_str.as_str()).expect("couldn't parse string to YAML"); serde_yaml::from_str(yaml_str.as_str()).expect("couldn't parse string to YAML");
let mut spec = yaml_value let spec = yaml_value
.get_mut("spec") .get_mut("spec")
.expect("couldn't get spec from yaml") .expect("couldn't get spec from yaml")
.as_mapping_mut() .as_mapping_mut()
@ -271,7 +270,7 @@ mod tests {
namespace: Some("test-ns".to_string()), namespace: Some("test-ns".to_string()),
project: "test-project".to_string(), project: "test-project".to_string(),
source: Source { source: Source {
repo_url: Url::parse("http://test").unwrap(), repo_url: "http://test".to_string(),
target_revision: None, target_revision: None,
chart: "test-chart".to_string(), chart: "test-chart".to_string(),
helm: Helm { helm: Helm {
@ -290,6 +289,7 @@ mod tests {
api_versions: vec![], api_versions: vec![],
namespace: None, namespace: None,
}, },
path: "".to_string(),
}, },
sync_policy: SyncPolicy { sync_policy: SyncPolicy {
automated: Automated { automated: Automated {

View File

@ -9,12 +9,9 @@ use crate::{
config::HARMONY_DATA_DIR, config::HARMONY_DATA_DIR,
data::Version, data::Version,
inventory::Inventory, inventory::Inventory,
modules::{ modules::application::{
application::{ Application, ApplicationFeature, HelmPackage, OCICompliant,
Application, ApplicationFeature, HelmPackage, OCICompliant, features::{ArgoApplication, ArgoHelmScore},
features::{ArgoApplication, ArgoHelmScore},
},
helm::chart::HelmChartScore,
}, },
score::Score, score::Score,
topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url}, topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url},
@ -162,7 +159,7 @@ impl<
info!("Pushed new helm chart {helm_chart}"); info!("Pushed new helm chart {helm_chart}");
error!("TODO Make building image configurable/skippable"); 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!("Pushed new docker image {image}");
info!("Installing ContinuousDelivery feature"); info!("Installing ContinuousDelivery feature");
@ -188,12 +185,12 @@ impl<
info!("Deploying to target {target:?}"); info!("Deploying to target {target:?}");
let score = ArgoHelmScore { let score = ArgoHelmScore {
namespace: "harmonydemo-staging".to_string(), namespace: "harmonydemo-staging".to_string(),
openshift: true, openshift: false,
domain: "argo.harmonydemo.apps.st.mcd".to_string(), domain: "argo.harmonydemo.apps.st.mcd".to_string(),
argo_apps: vec![ArgoApplication::from(CDApplicationConfig { argo_apps: vec![ArgoApplication::from(CDApplicationConfig {
// helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart/harmony-example-rust-webapp-chart --version 0.1.0 // helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart --version 0.1.0
version: Version::from("0.1.0").unwrap(), version: Version::from("0.1.0").unwrap(),
helm_chart_repo_url: Url::Url(url::Url::parse("oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart/harmony-example-rust-webapp-chart").unwrap()), helm_chart_repo_url: "hub.nationtech.io/harmony".to_string(),
helm_chart_name: "harmony-example-rust-webapp-chart".to_string(), helm_chart_name: "harmony-example-rust-webapp-chart".to_string(),
values_overrides: None, values_overrides: None,
name: "harmony-demo-rust-webapp".to_string(), name: "harmony-demo-rust-webapp".to_string(),
@ -225,7 +222,7 @@ impl<
/// more CD systems /// more CD systems
pub struct CDApplicationConfig { pub struct CDApplicationConfig {
pub version: Version, pub version: Version,
pub helm_chart_repo_url: Url, pub helm_chart_repo_url: String,
pub helm_chart_name: String, pub helm_chart_name: String,
pub values_overrides: Option<Value>, pub values_overrides: Option<Value>,
pub name: String, pub name: String,

View File

@ -1,5 +1,4 @@
use async_trait::async_trait; use async_trait::async_trait;
use k8s_openapi::Resource;
use log::error; use log::error;
use non_blank_string_rs::NonBlankString; use non_blank_string_rs::NonBlankString;
use serde::Serialize; use serde::Serialize;
@ -647,7 +646,7 @@ server:
# Argo CD server ingress configuration # Argo CD server ingress configuration
ingress: ingress:
# -- Enable an ingress resource for the Argo CD server # -- Enable an ingress resource for the Argo CD server
enabled: false enabled: true
# -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke` # -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke`
## Additional configuration might be required in related configuration sections ## Additional configuration might be required in related configuration sections
controller: generic controller: generic

View File

@ -4,13 +4,10 @@ use log::info;
use crate::{ use crate::{
inventory::Inventory, inventory::Inventory,
modules::{ modules::{
application::{Application, ApplicationFeature}, application::ApplicationFeature,
monitoring::{ monitoring::{
application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore, application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore,
kube_prometheus::{ kube_prometheus::types::{NamespaceSelector, ServiceMonitor},
helm_prometheus_alert_score::HelmPrometheusAlertingScore,
types::{NamespaceSelector, ServiceMonitor},
},
}, },
}, },
score::Score, score::Score,

View File

@ -416,7 +416,7 @@ ingress:
Expand the name of the chart. Expand the name of the chart.
*/}} */}}
{{- define "chart.name" -}} {{- define "chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- default .Chart.Name $.Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
{{/* {{/*
@ -424,7 +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). We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
*/}} */}}
{{- define "chart.fullname" -}} {{- define "chart.fullname" -}}
{{- $name := default .Chart.Name .Values.nameOverride }} {{- $name := default .Chart.Name $.Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }} {{- end }}
"#; "#;
@ -437,12 +437,12 @@ kind: Service
metadata: metadata:
name: {{ include "chart.fullname" . }} name: {{ include "chart.fullname" . }}
spec: spec:
type: {{ .Values.service.type }} type: {{ $.Values.service.type }}
ports: ports:
- port: {{ .Values.service.port }} - name: main
targetPort: 3000 port: {{ $.Values.service.port | default 3000 }}
targetPort: {{ $.Values.service.port | default 3000 }}
protocol: TCP protocol: TCP
name: http
selector: selector:
app: {{ include "chart.name" . }} app: {{ include "chart.name" . }}
"#; "#;
@ -455,7 +455,7 @@ kind: Deployment
metadata: metadata:
Review

Thanks for fixing my demo hardcoding crap 😅

Thanks for fixing my demo hardcoding crap 😅
name: {{ include "chart.fullname" . }} name: {{ include "chart.fullname" . }}
spec: spec:
replicas: {{ .Values.replicaCount }} replicas: {{ $.Values.replicaCount }}
selector: selector:
matchLabels: matchLabels:
app: {{ include "chart.name" . }} app: {{ include "chart.name" . }}
@ -466,28 +466,28 @@ spec:
spec: spec:
containers: containers:
- name: {{ .Chart.Name }} - name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }} imagePullPolicy: {{ $.Values.image.pullPolicy }}
ports: ports:
- name: http - name: main
containerPort: 3000 containerPort: {{ $.Values.service.port | default 3000 }}
protocol: TCP protocol: TCP
"#; "#;
fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?; fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?;
// Create templates/ingress.yaml // Create templates/ingress.yaml
let ingress_yaml = r#" let ingress_yaml = r#"
{{- if .Values.ingress.enabled -}} {{- if $.Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1 apiVersion: networking.k8s.io/v1
kind: Ingress kind: Ingress
metadata: metadata:
name: {{ include "chart.fullname" . }} name: {{ include "chart.fullname" . }}
annotations: annotations:
{{- toYaml .Values.ingress.annotations | nindent 4 }} {{- toYaml $.Values.ingress.annotations | nindent 4 }}
spec: spec:
{{- if .Values.ingress.tls }} {{- if $.Values.ingress.tls }}
tls: tls:
{{- range .Values.ingress.tls }} {{- range $.Values.ingress.tls }}
- hosts: - hosts:
{{- range .hosts }} {{- range .hosts }}
- {{ . | quote }} - {{ . | quote }}
@ -496,7 +496,7 @@ spec:
{{- end }} {{- end }}
{{- end }} {{- end }}
rules: rules:
{{- range .Values.ingress.hosts }} {{- range $.Values.ingress.hosts }}
- host: {{ .host | quote }} - host: {{ .host | quote }}
http: http:
paths: paths:
@ -507,7 +507,7 @@ spec:
service: service:
name: {{ include "chart.fullname" $ }} name: {{ include "chart.fullname" $ }}
port: port:
number: 3000 number: {{ $.Values.service.port | default 3000 }}
{{- end }} {{- end }}
{{- end }} {{- end }}
{{- end }} {{- end }}