Compare commits
	
		
			15 Commits
		
	
	
		
			88270ece61
			...
			2d74c66fc6
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2d74c66fc6 | |||
| cd8542258c | |||
| 472a3c1051 | |||
| 764fd6d451 | |||
| 78fffcd725 | |||
| e1133ea114 | |||
| d8e8a49745 | |||
| a7ba9be486 | |||
| 1c3669cb47 | |||
| 90b80b24bc | |||
| c879ca143f | |||
| bc2bd2f2f4 | |||
| 28978299c9 | |||
| 87f6afc249 | |||
| 254f392cb5 | 
							
								
								
									
										75
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										75
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -356,9 +356,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "cc" | name = "cc" | ||||||
| version = "1.2.19" | version = "1.2.20" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" | checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "shlex", |  "shlex", | ||||||
| ] | ] | ||||||
| @ -382,9 +382,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "chrono" | name = "chrono" | ||||||
| version = "0.4.40" | version = "0.4.41" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" | checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "android-tzdata", |  "android-tzdata", | ||||||
|  "iana-time-zone", |  "iana-time-zone", | ||||||
| @ -519,11 +519,20 @@ version = "0.1.16" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" | checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "getrandom 0.2.15", |  "getrandom 0.2.16", | ||||||
|  "once_cell", |  "once_cell", | ||||||
|  "tiny-keccak", |  "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]] | [[package]] | ||||||
| name = "core-foundation" | name = "core-foundation" | ||||||
| version = "0.9.4" | version = "0.9.4" | ||||||
| @ -1020,8 +1029,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", | ||||||
| @ -1289,9 +1298,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "getrandom" | name = "getrandom" | ||||||
| version = "0.2.15" | version = "0.2.16" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" | checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  "js-sys", |  "js-sys", | ||||||
| @ -1383,6 +1392,7 @@ version = "0.1.0" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "async-trait", |  "async-trait", | ||||||
|  "cidr", |  "cidr", | ||||||
|  |  "convert_case", | ||||||
|  "derive-new", |  "derive-new", | ||||||
|  "directories", |  "directories", | ||||||
|  "dockerfile_builder", |  "dockerfile_builder", | ||||||
| @ -1409,6 +1419,7 @@ dependencies = [ | |||||||
|  "serde-value", |  "serde-value", | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  "serde_yaml", |  "serde_yaml", | ||||||
|  |  "temp-file", | ||||||
|  "tokio", |  "tokio", | ||||||
|  "url", |  "url", | ||||||
|  "uuid", |  "uuid", | ||||||
| @ -2064,9 +2075,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "jiff" | name = "jiff" | ||||||
| version = "0.2.8" | version = "0.2.10" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0" | checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "jiff-static", |  "jiff-static", | ||||||
|  "log", |  "log", | ||||||
| @ -2077,9 +2088,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "jiff-static" | name = "jiff-static" | ||||||
| version = "0.2.8" | version = "0.2.10" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605" | checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
|  "quote", |  "quote", | ||||||
| @ -2238,9 +2249,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "libm" | name = "libm" | ||||||
| version = "0.2.11" | version = "0.2.13" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" | checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "libredfish" | name = "libredfish" | ||||||
| @ -2947,7 +2958,7 @@ version = "0.2.21" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "zerocopy 0.8.24", |  "zerocopy 0.8.25", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -3073,7 +3084,7 @@ version = "0.6.4" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "getrandom 0.2.15", |  "getrandom 0.2.16", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -3121,7 +3132,7 @@ version = "0.5.0" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" | checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "getrandom 0.2.15", |  "getrandom 0.2.16", | ||||||
|  "libredox", |  "libredox", | ||||||
|  "thiserror 2.0.12", |  "thiserror 2.0.12", | ||||||
| ] | ] | ||||||
| @ -3259,7 +3270,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "cc", |  "cc", | ||||||
|  "cfg-if", |  "cfg-if", | ||||||
|  "getrandom 0.2.15", |  "getrandom 0.2.16", | ||||||
|  "libc", |  "libc", | ||||||
|  "untrusted", |  "untrusted", | ||||||
|  "windows-sys 0.52.0", |  "windows-sys 0.52.0", | ||||||
| @ -3806,9 +3817,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "signal-hook-registry" | name = "signal-hook-registry" | ||||||
| version = "1.4.2" | version = "1.4.5" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "libc", |  "libc", | ||||||
| ] | ] | ||||||
| @ -3996,9 +4007,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "syn" | name = "syn" | ||||||
| version = "2.0.100" | version = "2.0.101" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" | checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
|  "quote", |  "quote", | ||||||
| @ -4079,6 +4090,12 @@ version = "1.0.1" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" | ||||||
| 
 | 
 | ||||||
|  | [[package]] | ||||||
|  | name = "temp-file" | ||||||
|  | version = "0.1.9" | ||||||
|  | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
|  | checksum = "b5ff282c3f91797f0acb021f3af7fffa8a78601f0f2fd0a9f79ee7dcf9a9af9e" | ||||||
|  | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tempfile" | name = "tempfile" | ||||||
| version = "3.19.1" | version = "3.19.1" | ||||||
| @ -4259,9 +4276,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "tokio-util" | name = "tokio-util" | ||||||
| version = "0.7.14" | version = "0.7.15" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" | checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "bytes", |  "bytes", | ||||||
|  "futures-core", |  "futures-core", | ||||||
| @ -5096,11 +5113,11 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "zerocopy" | name = "zerocopy" | ||||||
| version = "0.8.24" | version = "0.8.25" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" | checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "zerocopy-derive 0.8.24", |  "zerocopy-derive 0.8.25", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| @ -5116,9 +5133,9 @@ dependencies = [ | |||||||
| 
 | 
 | ||||||
| [[package]] | [[package]] | ||||||
| name = "zerocopy-derive" | name = "zerocopy-derive" | ||||||
| version = "0.8.24" | version = "0.8.25" | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" | checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef" | ||||||
| dependencies = [ | dependencies = [ | ||||||
|  "proc-macro2", |  "proc-macro2", | ||||||
|  "quote", |  "quote", | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ serde_yaml = "0.9.34" | |||||||
| serde-value = "0.7.0" | serde-value = "0.7.0" | ||||||
| http = "1.2.0" | http = "1.2.0" | ||||||
| inquire = "0.7.5" | inquire = "0.7.5" | ||||||
|  | convert_case =  "0.8.0" | ||||||
| 
 | 
 | ||||||
| [workspace.dependencies.uuid] | [workspace.dependencies.uuid] | ||||||
| version = "1.11.0" | version = "1.11.0" | ||||||
|  | |||||||
| @ -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 } | ||||||
|  | |||||||
| @ -1,3 +1,85 @@ | |||||||
| <?php | <?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] | #[tokio::main] | ||||||
| async fn 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 { |     let lamp_stack = LAMPScore { | ||||||
|         name: "harmony-lamp-demo".to_string(), |         name: "harmony-lamp-demo".to_string(), | ||||||
|         domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()), |         domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()), | ||||||
|         php_version: Version::from("8.4.4").unwrap(), |         php_version: Version::from("8.4.4").unwrap(), | ||||||
|  |         // This config can be extended as needed for more complicated configurations
 | ||||||
|         config: LAMPConfig { |         config: LAMPConfig { | ||||||
|             project_root: "./php".into(), |             project_root: "./php".into(), | ||||||
|  |             database_size: format!("2Gi").into(), | ||||||
|             ..Default::default() |             ..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( |     let mut maestro = Maestro::<K8sAnywhereTopology>::initialize( | ||||||
|         Inventory::autoload(), |         Inventory::autoload(), | ||||||
|         K8sAnywhereTopology::new(), |         K8sAnywhereTopology::new(), | ||||||
| @ -26,5 +40,7 @@ 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(); |     // 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" | semver = "1.0.23" | ||||||
| serde = { version = "1.0.209", features = ["derive"] } | serde = { version = "1.0.209", features = ["derive"] } | ||||||
| serde_json = "1.0.127" | serde_json = "1.0.127" | ||||||
| tokio = { workspace = true } | tokio.workspace = true | ||||||
| derive-new = { workspace = true } | derive-new.workspace = true | ||||||
| log = { workspace = true } | log.workspace = true | ||||||
| env_logger = { workspace = true } | env_logger.workspace = true | ||||||
| async-trait = { workspace = true } | async-trait.workspace = true | ||||||
| cidr = { workspace = true } | cidr.workspace = true | ||||||
| opnsense-config = { path = "../opnsense-config" } | opnsense-config = { path = "../opnsense-config" } | ||||||
| opnsense-config-xml = { path = "../opnsense-config-xml" } | opnsense-config-xml = { path = "../opnsense-config-xml" } | ||||||
| harmony_macros = { path = "../harmony_macros" } | harmony_macros = { path = "../harmony_macros" } | ||||||
| harmony_types = { path = "../harmony_types" } | harmony_types = { path = "../harmony_types" } | ||||||
| uuid = { workspace = true } | uuid.workspace = true | ||||||
| url = { workspace = true } | url.workspace = true | ||||||
| kube = { workspace = true } | kube.workspace = true | ||||||
| k8s-openapi = { workspace = true } | k8s-openapi.workspace = true | ||||||
| serde_yaml = { workspace = true } | serde_yaml.workspace = true | ||||||
| http = { workspace = true } | http.workspace = true | ||||||
| serde-value = { workspace = true } | serde-value.workspace = true | ||||||
| inquire.workspace = true | inquire.workspace = true | ||||||
| helm-wrapper-rs = "0.4.0" | helm-wrapper-rs = "0.4.0" | ||||||
| non-blank-string-rs = "1.0.4" | non-blank-string-rs = "1.0.4" | ||||||
| @ -37,3 +37,5 @@ k3d-rs = { path = "../k3d" } | |||||||
| directories = "6.0.0" | directories = "6.0.0" | ||||||
| lazy_static = "1.5.0" | lazy_static = "1.5.0" | ||||||
| dockerfile_builder = "0.1.5" | dockerfile_builder = "0.1.5" | ||||||
|  | temp-file = "0.1.9" | ||||||
|  | convert_case.workspace = true | ||||||
|  | |||||||
| @ -6,4 +6,8 @@ lazy_static! { | |||||||
|         .unwrap() |         .unwrap() | ||||||
|         .data_dir() |         .data_dir() | ||||||
|         .join("harmony"); |         .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) |         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 |     where | ||||||
|         K: Resource<Scope = NamespaceResourceScope> |         K: Resource<Scope = NamespaceResourceScope> | ||||||
|             + Clone |             + Clone | ||||||
| @ -49,7 +49,10 @@ impl K8sClient { | |||||||
|         <K as kube::Resource>::DynamicType: Default, |         <K as kube::Resource>::DynamicType: Default, | ||||||
|     { |     { | ||||||
|         for r in resource.iter() { |         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?; |             api.create(&PostParams::default(), &r).await?; | ||||||
|         } |         } | ||||||
|         todo!("") |         todo!("") | ||||||
|  | |||||||
| @ -1,5 +1,6 @@ | |||||||
|  | use std::sync::Arc; | ||||||
|  | 
 | ||||||
| use log::warn; | use log::warn; | ||||||
| use serde::Serialize; |  | ||||||
| use tokio::sync::OnceCell; | use tokio::sync::OnceCell; | ||||||
| 
 | 
 | ||||||
| use k8s_openapi::api::core::v1::Pod; | use k8s_openapi::api::core::v1::Pod; | ||||||
| @ -15,16 +16,17 @@ use crate::{ | |||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     maestro::Maestro, |     maestro::Maestro, | ||||||
|     modules::monitoring::monitoring_alerting::MonitoringAlertingStackScore, |     modules::monitoring::monitoring_alerting::MonitoringAlertingStackScore, | ||||||
|  |     score::Score, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::{HelmCommand, Topology}; | use super::{HelmCommand, K8sAnywhereTopology, Topology, k8s::K8sClient}; | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| struct MonitoringState { | struct MonitoringState { | ||||||
|     message: String, |     message: String, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug)] | #[derive(Debug)] | ||||||
| pub struct MonitoringAlertingTopology { | pub struct MonitoringAlertingTopology { | ||||||
|     monitoring_state: OnceCell<Option<MonitoringState>>, |     monitoring_state: OnceCell<Option<MonitoringState>>, | ||||||
| } | } | ||||||
| @ -73,30 +75,13 @@ impl MonitoringAlertingTopology { | |||||||
| 
 | 
 | ||||||
|         Ok(None) |         Ok(None) | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     async fn try_install_monitoring_stack( |  | ||||||
|         &self, |  | ||||||
|     ) -> Result<Option<MonitoringState>, InterpretError> { |  | ||||||
|         let inventory = Inventory::autoload(); |  | ||||||
|         let topology = MonitoringAlertingTopology::new(); |  | ||||||
|         let mut maestro = match Maestro::initialize(inventory, topology).await { |  | ||||||
|             Ok(m) => m, |  | ||||||
|             Err(e) => { |  | ||||||
|                 println!("failed to initialize Maestro: {}", e); |  | ||||||
|                 std::process::exit(1); |  | ||||||
|             } |  | ||||||
|         }; |  | ||||||
|         maestro.register_all(vec![Box::new(MonitoringAlertingStackScore::default())]); |  | ||||||
|         let state = match self.get_monitoring_state().await { |  | ||||||
|             Ok(_) => MonitoringState { |  | ||||||
|                 message: "Monitoring Stack Ready".to_string(), |  | ||||||
|             }, |  | ||||||
|             Err(_) => todo!(), |  | ||||||
|         }; |  | ||||||
|         Ok(Some(state)) |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | impl<T: Topology> Clone for Box<dyn Score<T>> { | ||||||
|  |     fn clone(&self) -> Box<dyn Score<T>> { | ||||||
|  |         self.clone_box() | ||||||
|  |     } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl Topology for MonitoringAlertingTopology { | impl Topology for MonitoringAlertingTopology { | ||||||
| @ -105,14 +90,16 @@ impl Topology for MonitoringAlertingTopology { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn ensure_ready(&self) -> Result<Outcome, InterpretError> { |     async fn ensure_ready(&self) -> Result<Outcome, InterpretError> { | ||||||
|         let state = if let Some(state) = self.get_monitoring_state().await? { |         if let Some(state) = self.get_monitoring_state().await? { | ||||||
|             state |             // Monitoring stack is already ready — stop app.
 | ||||||
|         } else { |             println!("{}", state.message); | ||||||
|             self.try_install_monitoring_stack().await? |             std::process::exit(0); | ||||||
|                 .ok_or_else(|| InterpretError::new("Failed to install monitoring stack".into()))? |         } | ||||||
|         }; |  | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::success(state.message)) |         // Monitoring not found — proceed with installation.
 | ||||||
|  |         Ok(Outcome::success( | ||||||
|  |             "Monitoring stack installation started.".to_string(), | ||||||
|  |         )) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,9 +6,13 @@ 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::str::FromStr; | ||||||
|  | use temp_file::TempFile; | ||||||
| 
 | 
 | ||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| pub struct HelmChartScore { | pub struct HelmChartScore { | ||||||
| @ -17,6 +21,11 @@ pub struct HelmChartScore { | |||||||
|     pub chart_name: NonBlankString, |     pub chart_name: NonBlankString, | ||||||
|     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 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 { | ||||||
| @ -48,16 +57,68 @@ impl<T: Topology + HelmCommand> Interpret<T> for HelmChartInterpret { | |||||||
|             .namespace |             .namespace | ||||||
|             .as_ref() |             .as_ref() | ||||||
|             .unwrap_or_else(|| todo!("Get namespace from active kubernetes cluster")); |             .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 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, | ||||||
|             &self.score.chart_name, |             &self.score.chart_name, | ||||||
|             self.score.chart_version.as_ref(), |             self.score.chart_version.as_ref(), | ||||||
|             self.score.values_overrides.as_ref(), |             self.score.values_overrides.as_ref(), | ||||||
|             None, |             yaml_path, | ||||||
|             None, |             Some(&helm_options), | ||||||
|         ); |         ); | ||||||
|  | 
 | ||||||
|         let status = match res { |         let status = match res { | ||||||
|             Ok(status) => status, |             Ok(status) => status, | ||||||
|             Err(err) => return Err(InterpretError::new(err.to_string())), |             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::Serialize; | ||||||
| use serde_json::json; | use serde_json::json; | ||||||
| 
 | 
 | ||||||
| @ -14,11 +15,13 @@ use super::resource::{K8sResourceInterpret, K8sResourceScore}; | |||||||
| pub struct K8sDeploymentScore { | pub struct K8sDeploymentScore { | ||||||
|     pub name: String, |     pub name: String, | ||||||
|     pub image: String, |     pub image: String, | ||||||
|  |     pub namespace: Option<String>, | ||||||
|  |     pub env_vars: serde_json::Value, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore { | impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore { | ||||||
|     fn create_interpret(&self) -> Box<dyn Interpret<T>> { |     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||||
|         let deployment: Deployment = serde_json::from_value(json!( |         let deployment = json!( | ||||||
|             { |             { | ||||||
|                 "metadata": { |                 "metadata": { | ||||||
|                     "name": self.name |                     "name": self.name | ||||||
| @ -38,18 +41,21 @@ impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore { | |||||||
|                         "spec": { |                         "spec": { | ||||||
|                             "containers": [ |                             "containers": [ | ||||||
|                                 { |                                 { | ||||||
|                                      "image": self.image, |                                     "image": self.image, | ||||||
|                                       "name": self.image |                                     "name": self.name, | ||||||
|  |                                     "imagePullPolicy": "IfNotPresent", | ||||||
|  |                                     "env": self.env_vars, | ||||||
|                                 } |                                 } | ||||||
|                             ] |                             ] | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         )) |         ); | ||||||
|         .unwrap(); | 
 | ||||||
|  |         let deployment: Deployment = serde_json::from_value(deployment).unwrap(); | ||||||
|         Box::new(K8sResourceInterpret { |         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 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() | ||||||
|  |     } | ||||||
|  | } | ||||||
| @ -14,12 +14,14 @@ use crate::{ | |||||||
| #[derive(Debug, Clone, Serialize)] | #[derive(Debug, Clone, Serialize)] | ||||||
| pub struct K8sResourceScore<K: Resource + std::fmt::Debug> { | pub struct K8sResourceScore<K: Resource + std::fmt::Debug> { | ||||||
|     pub resource: Vec<K>, |     pub resource: Vec<K>, | ||||||
|  |     pub namespace: Option<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> { | impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> { | ||||||
|     pub fn single(resource: K) -> Self { |     pub fn single(resource: K, namespace: Option<String>) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             resource: vec![resource], |             resource: vec![resource], | ||||||
|  |             namespace, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -77,7 +79,7 @@ where | |||||||
|             .k8s_client() |             .k8s_client() | ||||||
|             .await |             .await | ||||||
|             .expect("Environment should provide enough information to instanciate a client") |             .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?; |             .await?; | ||||||
| 
 | 
 | ||||||
|         Ok(Outcome::success( |         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::path::{Path, PathBuf}; | ||||||
|  | use std::str::FromStr; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::info; | use log::{debug, info}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
|  | use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; | ||||||
|  | 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 +23,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, | ||||||
| @ -25,6 +37,7 @@ pub struct LAMPScore { | |||||||
| pub struct LAMPConfig { | pub struct LAMPConfig { | ||||||
|     pub project_root: PathBuf, |     pub project_root: PathBuf, | ||||||
|     pub ssl_enabled: bool, |     pub ssl_enabled: bool, | ||||||
|  |     pub database_size: Option<String>, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for LAMPConfig { | impl Default for LAMPConfig { | ||||||
| @ -32,14 +45,16 @@ impl Default for LAMPConfig { | |||||||
|         LAMPConfig { |         LAMPConfig { | ||||||
|             project_root: Path::new("./src").to_path_buf(), |             project_root: Path::new("./src").to_path_buf(), | ||||||
|             ssl_enabled: true, |             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>> { |     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 +66,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 +86,54 @@ impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret { | |||||||
|         }; |         }; | ||||||
|         info!("LAMP docker image built {image_name}"); |         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 { |         let deployment_score = K8sDeploymentScore { | ||||||
|             name: <LAMPScore as Score<T>>::name(&self.score), |             name: <LAMPScore as Score<T>>::name(&self.score).to_case(Case::Kebab), | ||||||
|             image: image_name, |             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:?}"); |         info!("Deploying 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. [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 { |     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 { | 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 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(); |         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
 | ||||||
| @ -196,6 +270,13 @@ opcache.fast_shutdown=1 | |||||||
|              sed -i 's/ServerSignature On/ServerSignature Off/' /etc/apache2/conf-enabled/security.conf" |              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
 |         // Create a dedicated user for running Apache
 | ||||||
|         dockerfile.push(RUN::from( |         dockerfile.push(RUN::from( | ||||||
|             "groupadd -g 1000 appuser && \ |             "groupadd -g 1000 appuser && \ | ||||||
| @ -227,6 +308,43 @@ opcache.fast_shutdown=1 | |||||||
|         Ok(dockerfile_path) |         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>> { |     pub fn build_docker_image(&self) -> Result<String, Box<dyn std::error::Error>> { | ||||||
|         info!("Generating Dockerfile"); |         info!("Generating Dockerfile"); | ||||||
|         let dockerfile = self.build_dockerfile(&self.score)?; |         let dockerfile = self.build_dockerfile(&self.score)?; | ||||||
| @ -260,4 +378,8 @@ opcache.fast_shutdown=1 | |||||||
| 
 | 
 | ||||||
|         Ok(image_name) |         Ok(image_name) | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     fn get_namespace(&self) -> Option<NonBlankString> { | ||||||
|  |         Some(NonBlankString::from_str(&self.namespace).unwrap()) | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -38,5 +38,8 @@ additionalPrometheusRules: | |||||||
|         .unwrap(), |         .unwrap(), | ||||||
|         chart_version: None, |         chart_version: None, | ||||||
|         values_overrides: None, |         values_overrides: None, | ||||||
|  |         values_yaml: Some(values.to_string()), | ||||||
|  |         create_namespace: true, | ||||||
|  |         install_only: true, | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,34 +5,49 @@ use crate::{ | |||||||
|     data::{Id, Version}, |     data::{Id, Version}, | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     maestro::{self, Maestro}, |     maestro::Maestro, | ||||||
|     modules::helm::chart::HelmChartScore, |  | ||||||
|     score::{CloneBoxScore, Score}, |     score::{CloneBoxScore, Score}, | ||||||
|     topology::{K8sclient, Topology, monitoring_alerting::MonitoringAlertingTopology}, |     topology::{HelmCommand, Topology, monitoring_alerting::MonitoringAlertingTopology}, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::kube_prometheus::kube_prometheus_score; | use super::kube_prometheus::kube_prometheus_score; | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| pub struct MonitoringAlertingStackScore { | pub struct MonitoringAlertingStackScore { | ||||||
|     pub monitoring_stack: Vec<Box<dyn Score<MonitoringAlertingTopology>>>, |     pub monitoring_stack: Vec<Box<dyn Score<MonitoringAlertingTopology>>>, | ||||||
|  |     pub namespace: String, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | impl MonitoringAlertingStackScore { | ||||||
|  |     pub fn new( | ||||||
|  |         monitoring_stack: Vec<Box<dyn Score<MonitoringAlertingTopology>>>, | ||||||
|  |         namespace: String, | ||||||
|  |     ) -> Self { | ||||||
|  |         Self { | ||||||
|  |             monitoring_stack, | ||||||
|  |             namespace, | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl Default for MonitoringAlertingStackScore { | impl Default for MonitoringAlertingStackScore { | ||||||
|     fn default() -> Self { |     fn default() -> Self { | ||||||
|         let ns = "monitoring"; |         let ns = "monitoring"; | ||||||
|         Self { |         Self { | ||||||
|             monitoring_stack: vec![ |             monitoring_stack: vec![Box::new(kube_prometheus_score(ns))], | ||||||
|                 Box::new(kube_prometheus_score(ns)) as Box<dyn Score<MonitoringAlertingTopology>>, |             namespace: ns.to_string(), | ||||||
|             ], |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| impl Clone for MonitoringAlertingStackScore { | impl Clone for MonitoringAlertingStackScore { | ||||||
|     fn clone(&self) -> Self { |     fn clone(&self) -> Self { | ||||||
|         Self { |         Self { | ||||||
|             monitoring_stack: self.monitoring_stack.iter().map(|s| s.clone_box()).collect(), |             monitoring_stack: self | ||||||
|  |                 .monitoring_stack | ||||||
|  |                 .iter() | ||||||
|  |                 .map(|s| s.clone_box()) | ||||||
|  |                 .collect(), | ||||||
|  |             namespace: self.namespace.clone(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -44,17 +59,26 @@ impl Serialize for MonitoringAlertingStackScore { | |||||||
|     { |     { | ||||||
|         use serde::ser::SerializeStruct; |         use serde::ser::SerializeStruct; | ||||||
|         let mut s = serializer.serialize_struct("MonitoringAlertingStackScore", 1)?; |         let mut s = serializer.serialize_struct("MonitoringAlertingStackScore", 1)?; | ||||||
|         let monitoring_values: Vec<_> = self.monitoring_stack.iter().map(|m| m.serialize()).collect(); |         let monitoring_values: Vec<_> = self | ||||||
|  |             .monitoring_stack | ||||||
|  |             .iter() | ||||||
|  |             .map(|m| m.serialize()) | ||||||
|  |             .collect(); | ||||||
|         s.serialize_field("monitoring", &monitoring_values)?; |         s.serialize_field("monitoring", &monitoring_values)?; | ||||||
|         s.end() |         s.end() | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| impl<T: Topology> Score<T> for MonitoringAlertingStackScore { | impl<T:Topology> Score<T> for MonitoringAlertingStackScore { | ||||||
|     fn create_interpret(&self) -> Box<dyn Interpret<T>> { |     fn create_interpret(&self) -> Box<dyn Interpret<T>> { | ||||||
|         Box::new(MonitoringAlertingStackInterpret { |         Box::new(MonitoringAlertingStackInterpret { | ||||||
|             score: MonitoringAlertingStackScore { |             score: MonitoringAlertingStackScore { | ||||||
|                 monitoring_stack: self.monitoring_stack.iter().map(|s| s.clone_box()).collect(), |                 monitoring_stack: self | ||||||
|  |                     .monitoring_stack | ||||||
|  |                     .iter() | ||||||
|  |                     .map(|s| s.clone_box()) | ||||||
|  |                     .collect(), | ||||||
|  |                 namespace: self.namespace.clone(), | ||||||
|             }, |             }, | ||||||
|         }) |         }) | ||||||
|     } |     } | ||||||
| @ -69,15 +93,27 @@ struct MonitoringAlertingStackInterpret { | |||||||
|     pub score: MonitoringAlertingStackScore, |     pub score: MonitoringAlertingStackScore, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| 
 |  | ||||||
| #[async_trait] | #[async_trait] | ||||||
| impl<T: Topology> Interpret<T> for MonitoringAlertingStackInterpret { | impl <T: Topology> Interpret<T> for MonitoringAlertingStackInterpret { | ||||||
|     async fn execute( |     async fn execute( | ||||||
|         &self, |         &self, | ||||||
|         _inventory: &Inventory, |         _inventory: &Inventory, | ||||||
|         _topology: &T, |         _topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|        todo!() 
 |         let inventory = Inventory::autoload(); | ||||||
|  |         let topology = MonitoringAlertingTopology::new(); | ||||||
|  |         let mut maestro = match Maestro::initialize(inventory, topology).await { | ||||||
|  |             Ok(m) => m, | ||||||
|  |             Err(e) => { | ||||||
|  |                 println!("failed to initialize Maestro: {}", e); | ||||||
|  |                 std::process::exit(1); | ||||||
|  |             } | ||||||
|  |         }; | ||||||
|  |         maestro.register_all(self.score.monitoring_stack.clone()); | ||||||
|  |         Ok(Outcome::success(format!( | ||||||
|  |             "monitoring stack installed in {} namespace", | ||||||
|  |             self.score.namespace | ||||||
|  |         ))) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|  | |||||||
| @ -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