feat: add mariadb helm deployment to lamp interpreter
- Adds a `deploy_database` function to the `LAMPInterpret` struct to deploy a MariaDB database using Helm. - Integrates `HelmCommand` trait requirement to the `LAMPInterpret` struct. - Introduces `HelmChartScore` to manage MariaDB deployment. - Adds namespace configuration for helm deployments. - Updates trait bounds for `LAMPInterpret` to include `HelmCommand`. - Implements `get_namespace` function to retrieve the namespace.
This commit is contained in:
parent
254f392cb5
commit
87f6afc249
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -1020,8 +1020,8 @@ dependencies = [
|
|||||||
"cidr",
|
"cidr",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
"harmony_macros",
|
"harmony_macros",
|
||||||
"harmony_tui",
|
|
||||||
"harmony_types",
|
"harmony_types",
|
||||||
"log",
|
"log",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -8,7 +8,7 @@ publish = false
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
harmony = { path = "../../harmony" }
|
harmony = { path = "../../harmony" }
|
||||||
harmony_tui = { path = "../../harmony_tui" }
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
harmony_types = { path = "../../harmony_types" }
|
harmony_types = { path = "../../harmony_types" }
|
||||||
cidr = { workspace = true }
|
cidr = { workspace = true }
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
|
@ -26,5 +26,5 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
maestro.register_all(vec![Box::new(lamp_stack)]);
|
maestro.register_all(vec![Box::new(lamp_stack)]);
|
||||||
harmony_tui::init(maestro).await.unwrap();
|
harmony_cli::init(maestro, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ use crate::topology::{HelmCommand, Topology};
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use helm_wrapper_rs;
|
use helm_wrapper_rs;
|
||||||
use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor};
|
use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor};
|
||||||
|
use log::info;
|
||||||
pub use non_blank_string_rs::NonBlankString;
|
pub use non_blank_string_rs::NonBlankString;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::str::FromStr;
|
||||||
use temp_file::TempFile;
|
use temp_file::TempFile;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
@ -20,6 +22,10 @@ pub struct HelmChartScore {
|
|||||||
pub chart_version: Option<NonBlankString>,
|
pub chart_version: Option<NonBlankString>,
|
||||||
pub values_overrides: Option<HashMap<NonBlankString, String>>,
|
pub values_overrides: Option<HashMap<NonBlankString, String>>,
|
||||||
pub values_yaml: Option<String>,
|
pub values_yaml: Option<String>,
|
||||||
|
pub create_namespace: bool,
|
||||||
|
|
||||||
|
/// Wether to run `helm upgrade --install` under the hood or only install when not present
|
||||||
|
pub install_only: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + HelmCommand> Score<T> for HelmChartScore {
|
impl<T: Topology + HelmCommand> Score<T> for HelmChartScore {
|
||||||
@ -62,6 +68,47 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let helm_executor = DefaultHelmExecutor::new();
|
let helm_executor = DefaultHelmExecutor::new();
|
||||||
|
|
||||||
|
let mut helm_options = Vec::new();
|
||||||
|
if self.score.create_namespace {
|
||||||
|
helm_options.push(NonBlankString::from_str("--create-namespace").unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.score.install_only {
|
||||||
|
let chart_list = match helm_executor.list(Some(ns)) {
|
||||||
|
Ok(charts) => charts,
|
||||||
|
Err(e) => {
|
||||||
|
return Err(InterpretError::new(format!(
|
||||||
|
"Failed to list scores in namespace {:?} because of error : {}",
|
||||||
|
self.score.namespace, e
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if chart_list
|
||||||
|
.iter()
|
||||||
|
.any(|item| item.name == self.score.release_name.to_string())
|
||||||
|
{
|
||||||
|
info!(
|
||||||
|
"Release '{}' already exists in namespace '{}'. Skipping installation as install_only is true.",
|
||||||
|
self.score.release_name, ns
|
||||||
|
);
|
||||||
|
|
||||||
|
return Ok(Outcome::new(
|
||||||
|
InterpretStatus::SUCCESS,
|
||||||
|
format!(
|
||||||
|
"Helm Chart '{}' already installed to namespace {ns} and install_only=true",
|
||||||
|
self.score.release_name
|
||||||
|
),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
info!(
|
||||||
|
"Release '{}' not found in namespace '{}'. Proceeding with installation.",
|
||||||
|
self.score.release_name, ns
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let res = helm_executor.install_or_upgrade(
|
let res = helm_executor.install_or_upgrade(
|
||||||
&ns,
|
&ns,
|
||||||
&self.score.release_name,
|
&self.score.release_name,
|
||||||
@ -69,7 +116,7 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret {
|
|||||||
self.score.chart_version.as_ref(),
|
self.score.chart_version.as_ref(),
|
||||||
self.score.values_overrides.as_ref(),
|
self.score.values_overrides.as_ref(),
|
||||||
yaml_path,
|
yaml_path,
|
||||||
None,
|
Some(&helm_options),
|
||||||
);
|
);
|
||||||
|
|
||||||
let status = match res {
|
let status = match res {
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
pub mod deployment;
|
pub mod deployment;
|
||||||
|
pub mod namespace;
|
||||||
pub mod resource;
|
pub mod resource;
|
||||||
|
46
harmony/src/modules/k8s/namespace.rs
Normal file
46
harmony/src/modules/k8s/namespace.rs
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
use k8s_openapi::api::core::v1::Namespace;
|
||||||
|
use non_blank_string_rs::NonBlankString;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
interpret::Interpret,
|
||||||
|
score::Score,
|
||||||
|
topology::{K8sclient, Topology},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct K8sNamespaceScore {
|
||||||
|
pub name: Option<NonBlankString>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Topology + K8sclient> Score<T> for K8sNamespaceScore {
|
||||||
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
|
let name = match &self.name {
|
||||||
|
Some(name) => name,
|
||||||
|
None => todo!(
|
||||||
|
"Return NoOp interpret when no namespace specified or something that makes sense"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let _namespace: Namespace = serde_json::from_value(json!(
|
||||||
|
{
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"kind": "Namespace",
|
||||||
|
"metadata": {
|
||||||
|
"name": name,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
todo!(
|
||||||
|
"We currently only support namespaced ressources (see Scope = NamespaceResourceScope)"
|
||||||
|
);
|
||||||
|
// Box::new(K8sResourceInterpret {
|
||||||
|
// score: K8sResourceScore::single(namespace.clone()),
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"K8sNamespaceScore".to_string()
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,15 @@
|
|||||||
|
use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR};
|
||||||
|
use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder};
|
||||||
|
use non_blank_string_rs::NonBlankString;
|
||||||
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::info;
|
use log::info;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::topology::HelmCommand;
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{Id, Version},
|
data::{Id, Version},
|
||||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||||
@ -13,6 +19,8 @@ use crate::{
|
|||||||
topology::{K8sclient, Topology, Url},
|
topology::{K8sclient, Topology, Url},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::helm::chart::HelmChartScore;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct LAMPScore {
|
pub struct LAMPScore {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
@ -36,10 +44,11 @@ impl Default for LAMPConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + K8sclient> Score<T> for LAMPScore {
|
impl<T: Topology + K8sclient + HelmCommand> Score<T> for LAMPScore {
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
Box::new(LAMPInterpret {
|
Box::new(LAMPInterpret {
|
||||||
score: self.clone(),
|
score: self.clone(),
|
||||||
|
namespace: "harmony-lamp".to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,10 +60,11 @@ impl<T: Topology + K8sclient> Score<T> for LAMPScore {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct LAMPInterpret {
|
pub struct LAMPInterpret {
|
||||||
score: LAMPScore,
|
score: LAMPScore,
|
||||||
|
namespace: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret {
|
impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for LAMPInterpret {
|
||||||
async fn execute(
|
async fn execute(
|
||||||
&self,
|
&self,
|
||||||
inventory: &Inventory,
|
inventory: &Inventory,
|
||||||
@ -70,18 +80,23 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret {
|
|||||||
};
|
};
|
||||||
info!("LAMP docker image built {image_name}");
|
info!("LAMP docker image built {image_name}");
|
||||||
|
|
||||||
|
info!("Deploying database");
|
||||||
|
self.deploy_database(inventory, topology).await?;
|
||||||
|
|
||||||
let deployment_score = K8sDeploymentScore {
|
let deployment_score = K8sDeploymentScore {
|
||||||
name: <LAMPScore as Score<T>>::name(&self.score),
|
name: <LAMPScore as Score<T>>::name(&self.score),
|
||||||
image: image_name,
|
image: image_name,
|
||||||
};
|
};
|
||||||
|
|
||||||
info!("LAMP deployment_score {deployment_score:?}");
|
|
||||||
todo!();
|
|
||||||
deployment_score
|
deployment_score
|
||||||
.create_interpret()
|
.create_interpret()
|
||||||
.execute(inventory, topology)
|
.execute(inventory, topology)
|
||||||
.await?;
|
.await?;
|
||||||
todo!()
|
|
||||||
|
info!("LAMP deployment_score {deployment_score:?}");
|
||||||
|
todo!("1. Use HelmChartScore to deploy mariadb
|
||||||
|
2. Use deploymentScore to deploy lamp docker container
|
||||||
|
3. for remote clusters, push the image to some registry (use nationtech's for demos? push to the cluster's registry?)");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> InterpretName {
|
fn get_name(&self) -> InterpretName {
|
||||||
@ -101,15 +116,31 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR};
|
|
||||||
use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder};
|
|
||||||
use std::fs;
|
|
||||||
|
|
||||||
impl LAMPInterpret {
|
impl LAMPInterpret {
|
||||||
pub fn build_dockerfile(
|
async fn deploy_database<T: Topology + K8sclient + HelmCommand>(
|
||||||
&self,
|
&self,
|
||||||
score: &LAMPScore,
|
inventory: &Inventory,
|
||||||
) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
topology: &T,
|
||||||
|
) -> Result<Outcome, InterpretError> {
|
||||||
|
let score = HelmChartScore {
|
||||||
|
namespace: self.get_namespace(),
|
||||||
|
release_name: NonBlankString::from_str(&format!("{}-database", self.score.name))
|
||||||
|
.unwrap(),
|
||||||
|
chart_name: NonBlankString::from_str(
|
||||||
|
"oci://registry-1.docker.io/bitnamicharts/mariadb",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
chart_version: None,
|
||||||
|
values_overrides: None,
|
||||||
|
create_namespace: true,
|
||||||
|
install_only: true,
|
||||||
|
values_yaml: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
score.create_interpret().execute(inventory, topology).await
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_dockerfile(&self, score: &LAMPScore) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
let mut dockerfile = Dockerfile::new();
|
let mut dockerfile = Dockerfile::new();
|
||||||
|
|
||||||
// Use the PHP version from the score to determine the base image
|
// Use the PHP version from the score to determine the base image
|
||||||
@ -260,4 +291,8 @@ opcache.fast_shutdown=1
|
|||||||
|
|
||||||
Ok(image_name)
|
Ok(image_name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_namespace(&self) -> Option<NonBlankString> {
|
||||||
|
Some(NonBlankString::from_str(&self.namespace).unwrap())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,14 +4,14 @@ pub mod modules;
|
|||||||
|
|
||||||
pub use config::Config;
|
pub use config::Config;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
#[cfg(test)]
|
|
||||||
mod test {
|
#[cfg(e2e_test)]
|
||||||
|
mod e2e_test {
|
||||||
use opnsense_config_xml::StaticMap;
|
use opnsense_config_xml::StaticMap;
|
||||||
use std::net::Ipv4Addr;
|
use std::net::Ipv4Addr;
|
||||||
|
|
||||||
use crate::Config;
|
use crate::Config;
|
||||||
|
|
||||||
#[cfg(opnsenseendtoend)]
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_public_sdk() {
|
async fn test_public_sdk() {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
|
Loading…
Reference in New Issue
Block a user