monitoring-alerting #30
75
Cargo.lock
generated
75
Cargo.lock
generated
@ -356,9 +356,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.19"
|
||||
version = "1.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||
checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a"
|
||||
dependencies = [
|
||||
"shlex",
|
||||
]
|
||||
@ -382,9 +382,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
@ -519,11 +519,20 @@ version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@ -1020,8 +1029,8 @@ dependencies = [
|
||||
"cidr",
|
||||
"env_logger",
|
||||
"harmony",
|
||||
"harmony_cli",
|
||||
"harmony_macros",
|
||||
"harmony_tui",
|
||||
"harmony_types",
|
||||
"log",
|
||||
"tokio",
|
||||
@ -1289,9 +1298,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
@ -1383,6 +1392,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cidr",
|
||||
"convert_case",
|
||||
"derive-new",
|
||||
"directories",
|
||||
"dockerfile_builder",
|
||||
@ -1409,6 +1419,7 @@ dependencies = [
|
||||
"serde-value",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"temp-file",
|
||||
"tokio",
|
||||
"url",
|
||||
"uuid",
|
||||
@ -2064,9 +2075,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.8"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0"
|
||||
checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6"
|
||||
dependencies = [
|
||||
"jiff-static",
|
||||
"log",
|
||||
@ -2077,9 +2088,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.8"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605"
|
||||
checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2238,9 +2249,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.11"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
|
||||
checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72"
|
||||
|
||||
[[package]]
|
||||
name = "libredfish"
|
||||
@ -2947,7 +2958,7 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy 0.8.24",
|
||||
"zerocopy 0.8.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3073,7 +3084,7 @@ version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -3121,7 +3132,7 @@ version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror 2.0.12",
|
||||
]
|
||||
@ -3259,7 +3270,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"getrandom 0.2.15",
|
||||
"getrandom 0.2.16",
|
||||
"libc",
|
||||
"untrusted",
|
||||
"windows-sys 0.52.0",
|
||||
@ -3806,9 +3817,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.2"
|
||||
version = "1.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
|
||||
checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@ -3996,9 +4007,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
version = "2.0.101"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -4079,6 +4090,12 @@ version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "temp-file"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5ff282c3f91797f0acb021f3af7fffa8a78601f0f2fd0a9f79ee7dcf9a9af9e"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.19.1"
|
||||
@ -4259,9 +4276,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.14"
|
||||
version = "0.7.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034"
|
||||
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@ -5096,11 +5113,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.24"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879"
|
||||
checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
|
||||
dependencies = [
|
||||
"zerocopy-derive 0.8.24",
|
||||
"zerocopy-derive 0.8.25",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -5116,9 +5133,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.8.24"
|
||||
version = "0.8.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be"
|
||||
checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
@ -35,6 +35,7 @@ serde_yaml = "0.9.34"
|
||||
serde-value = "0.7.0"
|
||||
http = "1.2.0"
|
||||
inquire = "0.7.5"
|
||||
convert_case = "0.8.0"
|
||||
|
||||
[workspace.dependencies.uuid]
|
||||
version = "1.11.0"
|
||||
|
||||
@ -8,7 +8,7 @@ publish = false
|
||||
|
||||
[dependencies]
|
||||
harmony = { path = "../../harmony" }
|
||||
harmony_tui = { path = "../../harmony_tui" }
|
||||
harmony_cli = { path = "../../harmony_cli" }
|
||||
harmony_types = { path = "../../harmony_types" }
|
||||
cidr = { workspace = true }
|
||||
tokio = { workspace = true }
|
||||
|
||||
@ -1,3 +1,85 @@
|
||||
<?php
|
||||
print_r("Hello this is from PHP")
|
||||
|
||||
ini_set('display_errors', 1);
|
||||
error_reporting(E_ALL);
|
||||
|
||||
$host = getenv('MYSQL_HOST') ?: '';
|
||||
$user = getenv('MYSQL_USER') ?: 'root';
|
||||
$pass = getenv('MYSQL_PASSWORD') ?: '';
|
||||
$db = 'testfill';
|
||||
$charset = 'utf8mb4';
|
||||
|
||||
$dsn = "mysql:host=$host;charset=$charset";
|
||||
$options = [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
];
|
||||
|
||||
try {
|
||||
$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
$pdo->exec("CREATE DATABASE IF NOT EXISTS `$db`");
|
||||
$pdo->exec("USE `$db`");
|
||||
$pdo->exec("
|
||||
CREATE TABLE IF NOT EXISTS filler (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
data LONGBLOB
|
||||
)
|
||||
");
|
||||
} catch (\PDOException $e) {
|
||||
die("❌ DB connection failed: " . $e->getMessage());
|
||||
}
|
||||
|
||||
function getDbStats($pdo, $db) {
|
||||
$stmt = $pdo->query("
|
||||
SELECT
|
||||
ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2) AS total_size_gb,
|
||||
SUM(table_rows) AS total_rows
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = '$db'
|
||||
");
|
||||
$result = $stmt->fetch();
|
||||
$sizeGb = $result['total_size_gb'] ?? '0';
|
||||
$rows = $result['total_rows'] ?? '0';
|
||||
$avgMb = ($rows > 0) ? round(($sizeGb * 1024) / $rows, 2) : 0;
|
||||
return [$sizeGb, $rows, $avgMb];
|
||||
}
|
||||
|
||||
list($dbSize, $rowCount, $avgRowMb) = getDbStats($pdo, $db);
|
||||
|
||||
$message = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['fill'])) {
|
||||
$iterations = 1024;
|
||||
$data = str_repeat(random_bytes(1024), 1024); // 1MB
|
||||
$stmt = $pdo->prepare("INSERT INTO filler (data) VALUES (:data)");
|
||||
|
||||
for ($i = 0; $i < $iterations; $i++) {
|
||||
$stmt->execute([':data' => $data]);
|
||||
}
|
||||
|
||||
list($dbSize, $rowCount, $avgRowMb) = getDbStats($pdo, $db);
|
||||
|
||||
$message = "<p style='color: green;'>✅ 1GB inserted into MariaDB successfully.</p>";
|
||||
}
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>MariaDB Filler</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>MariaDB Storage Filler</h1>
|
||||
<?= $message ?>
|
||||
<ul>
|
||||
<li><strong>📦 MariaDB Used Size:</strong> <?= $dbSize ?> GB</li>
|
||||
<li><strong>📊 Total Rows:</strong> <?= $rowCount ?></li>
|
||||
<li><strong>📐 Average Row Size:</strong> <?= $avgRowMb ?> MB</li>
|
||||
</ul>
|
||||
|
||||
<form method="post">
|
||||
<button name="fill" value="1" type="submit">Insert 1GB into DB</button>
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@ -8,17 +8,31 @@ use harmony::{
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// let _ = env_logger::Builder::from_default_env().filter_level(log::LevelFilter::Info).try_init();
|
||||
// This here is the whole configuration to
|
||||
// - setup a local K3D cluster
|
||||
// - Build a docker image with the PHP project builtin and production grade settings
|
||||
// - Deploy a mariadb database using a production grade helm chart
|
||||
// - Deploy the new container using a kubernetes deployment
|
||||
// - Configure networking between the PHP container and the database
|
||||
// - Provision a public route and an SSL certificate automatically on production environments
|
||||
//
|
||||
// Enjoy :)
|
||||
let lamp_stack = LAMPScore {
|
||||
name: "harmony-lamp-demo".to_string(),
|
||||
domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()),
|
||||
php_version: Version::from("8.4.4").unwrap(),
|
||||
// This config can be extended as needed for more complicated configurations
|
||||
config: LAMPConfig {
|
||||
project_root: "./php".into(),
|
||||
database_size: format!("2Gi").into(),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
// You can choose the type of Topology you want, we suggest starting with the
|
||||
// K8sAnywhereTopology as it is the most automatic one that enables you to easily deploy
|
||||
// locally, to development environment from a CI, to staging, and to production with settings
|
||||
// that automatically adapt to each environment grade.
|
||||
let mut maestro = Maestro::<K8sAnywhereTopology>::initialize(
|
||||
Inventory::autoload(),
|
||||
K8sAnywhereTopology::new(),
|
||||
@ -26,5 +40,7 @@ async fn main() {
|
||||
.await
|
||||
.unwrap();
|
||||
maestro.register_all(vec![Box::new(lamp_stack)]);
|
||||
harmony_tui::init(maestro).await.unwrap();
|
||||
// Here we bootstrap the CLI, this gives some nice features if you need them
|
||||
harmony_cli::init(maestro, None).await.unwrap();
|
||||
}
|
||||
// That's it, end of the infra as code.
|
||||
|
||||
@ -13,23 +13,23 @@ rust-ipmi = "0.1.1"
|
||||
semver = "1.0.23"
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
serde_json = "1.0.127"
|
||||
tokio = { workspace = true }
|
||||
derive-new = { workspace = true }
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
cidr = { workspace = true }
|
||||
tokio.workspace = true
|
||||
derive-new.workspace = true
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
async-trait.workspace = true
|
||||
cidr.workspace = true
|
||||
opnsense-config = { path = "../opnsense-config" }
|
||||
opnsense-config-xml = { path = "../opnsense-config-xml" }
|
||||
harmony_macros = { path = "../harmony_macros" }
|
||||
harmony_types = { path = "../harmony_types" }
|
||||
uuid = { workspace = true }
|
||||
url = { workspace = true }
|
||||
kube = { workspace = true }
|
||||
k8s-openapi = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
http = { workspace = true }
|
||||
serde-value = { workspace = true }
|
||||
uuid.workspace = true
|
||||
url.workspace = true
|
||||
kube.workspace = true
|
||||
k8s-openapi.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
http.workspace = true
|
||||
serde-value.workspace = true
|
||||
inquire.workspace = true
|
||||
helm-wrapper-rs = "0.4.0"
|
||||
non-blank-string-rs = "1.0.4"
|
||||
@ -37,3 +37,5 @@ k3d-rs = { path = "../k3d" }
|
||||
directories = "6.0.0"
|
||||
lazy_static = "1.5.0"
|
||||
dockerfile_builder = "0.1.5"
|
||||
temp-file = "0.1.9"
|
||||
convert_case.workspace = true
|
||||
|
||||
@ -6,4 +6,8 @@ lazy_static! {
|
||||
.unwrap()
|
||||
.data_dir()
|
||||
.join("harmony");
|
||||
pub static ref REGISTRY_URL: String = std::env::var("HARMONY_REGISTRY_URL")
|
||||
.unwrap_or_else(|_| "hub.nationtech.io".to_string());
|
||||
pub static ref REGISTRY_PROJECT: String =
|
||||
std::env::var("HARMONY_REGISTRY_PROJECT").unwrap_or_else(|_| "harmony".to_string());
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ impl K8sClient {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn apply_namespaced<K>(&self, resource: &Vec<K>) -> Result<K, Error>
|
||||
pub async fn apply_namespaced<K>(&self, resource: &Vec<K>, ns: Option<&str>) -> Result<K, Error>
|
||||
where
|
||||
K: Resource<Scope = NamespaceResourceScope>
|
||||
+ Clone
|
||||
@ -49,7 +49,10 @@ impl K8sClient {
|
||||
<K as kube::Resource>::DynamicType: Default,
|
||||
{
|
||||
for r in resource.iter() {
|
||||
let api: Api<K> = Api::default_namespaced(self.client.clone());
|
||||
let api: Api<K> = match ns {
|
||||
Some(ns) => Api::namespaced(self.client.clone(), ns),
|
||||
None => Api::default_namespaced(self.client.clone()),
|
||||
};
|
||||
api.create(&PostParams::default(), &r).await?;
|
||||
}
|
||||
todo!("")
|
||||
|
||||
@ -6,9 +6,13 @@ use crate::topology::{HelmCommand, Topology};
|
||||
use async_trait::async_trait;
|
||||
use helm_wrapper_rs;
|
||||
use helm_wrapper_rs::blocking::{DefaultHelmExecutor, HelmExecutor};
|
||||
use log::info;
|
||||
pub use non_blank_string_rs::NonBlankString;
|
||||
use serde::Serialize;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use temp_file::TempFile;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct HelmChartScore {
|
||||
@ -17,6 +21,11 @@ pub struct HelmChartScore {
|
||||
pub chart_name: NonBlankString,
|
||||
pub chart_version: Option<NonBlankString>,
|
||||
pub values_overrides: Option<HashMap<NonBlankString, 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 {
|
||||
@ -48,16 +57,68 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret {
|
||||
.namespace
|
||||
.as_ref()
|
||||
.unwrap_or_else(|| todo!("Get namespace from active kubernetes cluster"));
|
||||
|
||||
let tf: TempFile;
|
||||
let yaml_path: Option<&Path> = match self.score.values_yaml.as_ref() {
|
||||
Some(yaml_str) => {
|
||||
tf = temp_file::with_contents(yaml_str.as_bytes());
|
||||
Some(tf.path())
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
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(
|
||||
&ns,
|
||||
&self.score.release_name,
|
||||
&self.score.chart_name,
|
||||
self.score.chart_version.as_ref(),
|
||||
self.score.values_overrides.as_ref(),
|
||||
None,
|
||||
None,
|
||||
yaml_path,
|
||||
Some(&helm_options),
|
||||
);
|
||||
|
||||
let status = match res {
|
||||
Ok(status) => status,
|
||||
Err(err) => return Err(InterpretError::new(err.to_string())),
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use k8s_openapi::api::apps::v1::Deployment;
|
||||
use k8s_openapi::{DeepMerge, api::apps::v1::Deployment};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
@ -14,11 +15,13 @@ use super::resource::{K8sResourceInterpret, K8sResourceScore};
|
||||
pub struct K8sDeploymentScore {
|
||||
pub name: String,
|
||||
pub image: String,
|
||||
pub namespace: Option<String>,
|
||||
pub env_vars: serde_json::Value,
|
||||
}
|
||||
|
||||
impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||
let deployment: Deployment = serde_json::from_value(json!(
|
||||
let deployment = json!(
|
||||
{
|
||||
"metadata": {
|
||||
"name": self.name
|
||||
@ -38,18 +41,21 @@ impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
||||
"spec": {
|
||||
"containers": [
|
||||
{
|
||||
"image": self.image,
|
||||
"name": self.image
|
||||
"image": self.image,
|
||||
"name": self.name,
|
||||
"imagePullPolicy": "IfNotPresent",
|
||||
"env": self.env_vars,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let deployment: Deployment = serde_json::from_value(deployment).unwrap();
|
||||
Box::new(K8sResourceInterpret {
|
||||
score: K8sResourceScore::single(deployment.clone()),
|
||||
score: K8sResourceScore::single(deployment.clone(), self.namespace.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -1,2 +1,3 @@
|
||||
pub mod deployment;
|
||||
pub mod namespace;
|
||||
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()
|
||||
}
|
||||
}
|
||||
@ -14,12 +14,14 @@ use crate::{
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct K8sResourceScore<K: Resource + std::fmt::Debug> {
|
||||
pub resource: Vec<K>,
|
||||
pub namespace: Option<String>,
|
||||
}
|
||||
|
||||
impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> {
|
||||
pub fn single(resource: K) -> Self {
|
||||
pub fn single(resource: K, namespace: Option<String>) -> Self {
|
||||
Self {
|
||||
resource: vec![resource],
|
||||
namespace,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,7 +79,7 @@ where
|
||||
.k8s_client()
|
||||
.await
|
||||
.expect("Environment should provide enough information to instanciate a client")
|
||||
.apply_namespaced(&self.score.resource)
|
||||
.apply_namespaced(&self.score.resource, self.score.namespace.as_deref())
|
||||
.await?;
|
||||
|
||||
Ok(Outcome::success(
|
||||
|
||||
@ -1,9 +1,19 @@
|
||||
use convert_case::{Case, Casing};
|
||||
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 serde_json::json;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::info;
|
||||
use log::{debug, info};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
|
||||
use crate::topology::HelmCommand;
|
||||
use crate::{
|
||||
data::{Id, Version},
|
||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||
@ -13,6 +23,8 @@ use crate::{
|
||||
topology::{K8sclient, Topology, Url},
|
||||
};
|
||||
|
||||
use super::helm::chart::HelmChartScore;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct LAMPScore {
|
||||
pub name: String,
|
||||
@ -25,6 +37,7 @@ pub struct LAMPScore {
|
||||
pub struct LAMPConfig {
|
||||
pub project_root: PathBuf,
|
||||
pub ssl_enabled: bool,
|
||||
pub database_size: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for LAMPConfig {
|
||||
@ -32,14 +45,16 @@ impl Default for LAMPConfig {
|
||||
LAMPConfig {
|
||||
project_root: Path::new("./src").to_path_buf(),
|
||||
ssl_enabled: true,
|
||||
database_size: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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>> {
|
||||
Box::new(LAMPInterpret {
|
||||
score: self.clone(),
|
||||
namespace: "harmony-lamp".to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -51,10 +66,11 @@ impl<T: Topology + K8sclient> Score<T> for LAMPScore {
|
||||
#[derive(Debug)]
|
||||
pub struct LAMPInterpret {
|
||||
score: LAMPScore,
|
||||
namespace: String,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret {
|
||||
impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for LAMPInterpret {
|
||||
async fn execute(
|
||||
&self,
|
||||
inventory: &Inventory,
|
||||
@ -70,18 +86,54 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret {
|
||||
};
|
||||
info!("LAMP docker image built {image_name}");
|
||||
|
||||
let remote_name = match self.push_docker_image(&image_name) {
|
||||
Ok(remote_name) => remote_name,
|
||||
Err(e) => {
|
||||
return Err(InterpretError::new(format!(
|
||||
"Could not push docker image {e}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
info!("LAMP docker image pushed to {remote_name}");
|
||||
|
||||
info!("Deploying database");
|
||||
self.deploy_database(inventory, topology).await?;
|
||||
|
||||
let base_name = self.score.name.to_case(Case::Kebab);
|
||||
let secret_name = format!("{}-database-mariadb", base_name);
|
||||
|
||||
let deployment_score = K8sDeploymentScore {
|
||||
name: <LAMPScore as Score<T>>::name(&self.score),
|
||||
image: image_name,
|
||||
name: <LAMPScore as Score<T>>::name(&self.score).to_case(Case::Kebab),
|
||||
image: remote_name,
|
||||
namespace: self.get_namespace().map(|nbs| nbs.to_string()),
|
||||
env_vars: json!([
|
||||
{
|
||||
"name": "MYSQL_PASSWORD",
|
||||
"valueFrom": {
|
||||
"secretKeyRef": {
|
||||
"name": secret_name,
|
||||
"key": "mariadb-root-password"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MYSQL_HOST",
|
||||
"value": secret_name
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
info!("LAMP deployment_score {deployment_score:?}");
|
||||
todo!();
|
||||
info!("Deploying score {deployment_score:#?}");
|
||||
|
||||
deployment_score
|
||||
.create_interpret()
|
||||
.execute(inventory, topology)
|
||||
.await?;
|
||||
todo!()
|
||||
|
||||
info!("LAMP deployment_score {deployment_score:?}");
|
||||
todo!("1. [x] Use HelmChartScore to deploy mariadb
|
||||
2. [x] 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 {
|
||||
@ -101,15 +153,37 @@ 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 {
|
||||
pub fn build_dockerfile(
|
||||
async fn deploy_database<T: Topology + K8sclient + HelmCommand>(
|
||||
&self,
|
||||
score: &LAMPScore,
|
||||
) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||
inventory: &Inventory,
|
||||
topology: &T,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
let mut values_overrides = HashMap::new();
|
||||
if let Some(database_size) = self.score.config.database_size.clone() {
|
||||
values_overrides.insert(
|
||||
NonBlankString::from_str("primary.persistence.size").unwrap(),
|
||||
database_size,
|
||||
);
|
||||
}
|
||||
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: Some(values_overrides),
|
||||
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();
|
||||
|
||||
// Use the PHP version from the score to determine the base image
|
||||
@ -196,6 +270,13 @@ opcache.fast_shutdown=1
|
||||
sed -i 's/ServerSignature On/ServerSignature Off/' /etc/apache2/conf-enabled/security.conf"
|
||||
));
|
||||
|
||||
// Set env vars
|
||||
dockerfile.push(RUN::from(
|
||||
"echo 'PassEnv MYSQL_PASSWORD' >> /etc/apache2/sites-available/000-default.conf \
|
||||
&& echo 'PassEnv MYSQL_USER' >> /etc/apache2/sites-available/000-default.conf \
|
||||
&& echo 'PassEnv MYSQL_HOST' >> /etc/apache2/sites-available/000-default.conf",
|
||||
));
|
||||
|
||||
// Create a dedicated user for running Apache
|
||||
dockerfile.push(RUN::from(
|
||||
"groupadd -g 1000 appuser && \
|
||||
@ -227,6 +308,43 @@ opcache.fast_shutdown=1
|
||||
Ok(dockerfile_path)
|
||||
}
|
||||
|
||||
fn check_output(
|
||||
&self,
|
||||
output: &std::process::Output,
|
||||
msg: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !output.status.success() {
|
||||
return Err(format!("{msg}: {}", String::from_utf8_lossy(&output.stderr)).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_docker_image(&self, image_name: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let full_tag = format!("{}/{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT, &image_name);
|
||||
let output = std::process::Command::new("docker")
|
||||
.args(["tag", image_name, &full_tag])
|
||||
.output()?;
|
||||
self.check_output(&output, "Tagging docker image failed")?;
|
||||
|
||||
debug!(
|
||||
"docker tag output {} {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let output = std::process::Command::new("docker")
|
||||
.args(["push", &full_tag])
|
||||
.output()?;
|
||||
self.check_output(&output, "Pushing docker image failed")?;
|
||||
debug!(
|
||||
"docker push output {} {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
Ok(full_tag)
|
||||
}
|
||||
|
||||
pub fn build_docker_image(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
info!("Generating Dockerfile");
|
||||
let dockerfile = self.build_dockerfile(&self.score)?;
|
||||
@ -260,4 +378,8 @@ opcache.fast_shutdown=1
|
||||
|
||||
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 error::Error;
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
#[cfg(e2e_test)]
|
||||
mod e2e_test {
|
||||
use opnsense_config_xml::StaticMap;
|
||||
use std::net::Ipv4Addr;
|
||||
|
||||
use crate::Config;
|
||||
|
||||
#[cfg(opnsenseendtoend)]
|
||||
#[tokio::test]
|
||||
async fn test_public_sdk() {
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user