From 8ee4403567b32fb9e8e43f42b6c3bd01f67bd13c Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Sat, 25 Apr 2026 09:34:18 -0400 Subject: [PATCH] feat: Add openwebui installation example --- Cargo.lock | 13 ++++ examples/openwebui/Cargo.toml | 16 +++++ examples/openwebui/src/main.rs | 101 ++++++++++++++++++++++++++++++ harmony/src/modules/helm/chart.rs | 70 +++++++++++---------- 4 files changed, 166 insertions(+), 34 deletions(-) create mode 100644 examples/openwebui/Cargo.toml create mode 100644 examples/openwebui/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 86a77a4b..5bef6a74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2944,6 +2944,19 @@ dependencies = [ "url", ] +[[package]] +name = "example-openwebui" +version = "0.1.0" +dependencies = [ + "clap", + "harmony", + "harmony_cli", + "harmony_macros", + "harmony_types", + "tokio", + "url", +] + [[package]] name = "example-operatorhub-catalogsource" version = "0.1.0" diff --git a/examples/openwebui/Cargo.toml b/examples/openwebui/Cargo.toml new file mode 100644 index 00000000..3fc97097 --- /dev/null +++ b/examples/openwebui/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "example-openwebui" +edition = "2024" +version.workspace = true +readme.workspace = true +license.workspace = true +publish = false + +[dependencies] +harmony = { path = "../../harmony" } +harmony_cli = { path = "../../harmony_cli" } +harmony_macros = { path = "../../harmony_macros" } +harmony_types = { path = "../../harmony_types" } +tokio = { workspace = true } +url = { workspace = true } +clap = { workspace = true } \ No newline at end of file diff --git a/examples/openwebui/src/main.rs b/examples/openwebui/src/main.rs new file mode 100644 index 00000000..26bb3939 --- /dev/null +++ b/examples/openwebui/src/main.rs @@ -0,0 +1,101 @@ +use std::str::FromStr; + +use harmony::{ + inventory::Inventory, + modules::helm::chart::{HelmChartScore, HelmRepository, NonBlankString}, + topology::{K8sAnywhereTopology, TlsRouter, Topology}, +}; +use harmony_macros::hurl; + +#[tokio::main] +async fn main() { + harmony_cli::cli_logger::init(); + + let ollama_url = "http://your-ollama-server:11434"; + deploy(ollama_url).await +} + +async fn deploy(ollama_url: &str) { + let topology = K8sAnywhereTopology::from_env(); + topology.ensure_ready().await.unwrap(); + + let domain = topology + .get_internal_domain() + .await + .unwrap() + .expect("cluster ingress domain not found — ensure this is an OKD cluster"); + + let ingress_host = format!("openwebui.{}", domain); + + let webui_secret_key = "CHANGE-ME-TO-A-STRONG-RANDOM-SECRET"; + + let values_yaml = format!( + r#"ollama: + enabled: false +ollamaUrls: + - "{ollama_url}" +pipelines: + enabled: false +tika: + enabled: false +websocket: + enabled: false +replicaCount: 1 +persistence: + enabled: true + size: 5Gi + accessModes: + - ReadWriteOnce + storageClass: "" +extraEnvVars: + - name: WEBUI_SECRET_KEY + value: "{webui_secret_key}" +podSecurityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault +containerSecurityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL +ingress: + enabled: true + annotations: + route.openshift.io/termination: edge + host: "{ingress_host}" + tls: true +service: + type: ClusterIP + port: 80 + containerPort: 8080"#, + ollama_url = ollama_url, + ingress_host = ingress_host, + webui_secret_key = webui_secret_key, + ); + + let openwebui = HelmChartScore { + namespace: Some(NonBlankString::from_str("openwebui").unwrap()), + release_name: NonBlankString::from_str("openwebui").unwrap(), + chart_name: NonBlankString::from_str("open-webui/open-webui").unwrap(), + chart_version: None, + values_overrides: None, + values_yaml: Some(values_yaml), + create_namespace: true, + install_only: false, + repository: Some(HelmRepository::new( + "open-webui".to_string(), + hurl!("https://open-webui.github.io/helm-charts/"), + true, + )), + }; + + harmony_cli::run( + Inventory::autoload(), + topology, + vec![Box::new(openwebui)], + None, + ) + .await + .unwrap(); +} diff --git a/harmony/src/modules/helm/chart.rs b/harmony/src/modules/helm/chart.rs index cbdc7cb5..01115978 100644 --- a/harmony/src/modules/helm/chart.rs +++ b/harmony/src/modules/helm/chart.rs @@ -205,43 +205,45 @@ impl Interpret for HelmChartInterpret { .unwrap_or_else(|| todo!("Get namespace from active kubernetes cluster")); let ns_str = ns.to_string(); - if let Some(installed_chart) = self.find_installed_release(topology, &ns_str)? { - return match self.expected_chart_field() { - Some(expected) - if Self::normalize_chart_field(&expected) - == Self::normalize_chart_field(&installed_chart) => - { - warn!( - "Helm release '{}' already installed at desired version ('{}'); skipping.", - self.score.release_name, installed_chart - ); - Ok(Outcome::success(format!( - "Helm Chart {} already at desired version", - self.score.release_name - ))) - } - Some(expected) => Err(InterpretError::new(format!( - "Helm release '{}' already installed as '{}', but score requests '{}'. \ - Refusing to upgrade/downgrade; resolve manually.", - self.score.release_name, installed_chart, expected - ))), - None => { - warn!( - "Helm release '{}' already installed as '{}'; score has no pinned \ - chart_version so skipping re-install.", - self.score.release_name, installed_chart - ); - Ok(Outcome::success(format!( - "Helm Chart {} already installed (version not pinned)", - self.score.release_name - ))) - } - }; - } - self.add_repo(topology)?; let mut args = if self.score.install_only { + // In install only mode we don't want to fail all the time, only when requesting + // something that looks like an upgrade + if let Some(installed_chart) = self.find_installed_release(topology, &ns_str)? { + return match self.expected_chart_field() { + Some(expected) + if Self::normalize_chart_field(&expected) + == Self::normalize_chart_field(&installed_chart) => + { + warn!( + "Helm release '{}' already installed at desired version ('{}'); skipping.", + self.score.release_name, installed_chart + ); + Ok(Outcome::success(format!( + "Helm Chart {} already at desired version", + self.score.release_name + ))) + } + Some(expected) => Err(InterpretError::new(format!( + "Helm release '{}' already installed as '{}', but score requests '{}'. \ + Refusing to upgrade/downgrade; resolve manually.", + self.score.release_name, installed_chart, expected + ))), + None => { + warn!( + "Helm release '{}' already installed as '{}'; score has no pinned \ + chart_version so skipping re-install.", + self.score.release_name, installed_chart + ); + Ok(Outcome::success(format!( + "Helm Chart {} already installed (version not pinned)", + self.score.release_name + ))) + } + }; + } + vec!["install"] } else { vec!["upgrade", "--install"] -- 2.39.5