fix: unjank the demo #85

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

3
.gitignore vendored
View File

@ -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
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` ?

1
Cargo.lock generated
View File

@ -1739,6 +1739,7 @@ name = "harmony"
version = "0.1.0"
dependencies = [
"async-trait",
"base64 0.22.1",
"bollard",
"chrono",
"cidr",

View File

@ -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::<Vec<&str>>()[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 {}),
],

Our users have no clue what ntfy or prometheus is, this is leaking Harmony's internals. Alerting is just a feature of the infrastructure that they do not need to know how it is implemented.

Our users have no clue what ntfy or prometheus is, this is leaking Harmony's internals. Alerting is just a feature of the infrastructure that they do not need to know how it is implemented.
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();
}

View File

@ -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

View File

@ -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()

View File

@ -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<T: Topology + HelmCommand + 'static + TenantManager> ApplicationFeature<T> for Monitoring {
impl<T: Topology + HelmCommand + K8sclient + 'static + TenantManager> ApplicationFeature<T>
taha marked this conversation as resolved Outdated
Outdated
Review

if we add pub application: Arc to monitoring we can use the application name as namespace so that it is consistent with the way continuous _delivery feature gets the namespace

if we add pub application: Arc<dyn Application> to monitoring we can use the application name as namespace so that it is consistent with the way continuous _delivery feature gets the namespace
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
taha marked this conversation as resolved
Review

rather than hardcoding namespace here

rather than hardcoding namespace here
.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::<Vec<&str>>()[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],
};

View File

@ -59,9 +59,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> 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 {

View File

@ -360,7 +360,11 @@ impl RustWebapp {
image_url: &str,
) -> Result<PathBuf, Box<dyn std::error::Error>> {
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.