Compare commits
	
		
			No commits in common. "5cc93d3107823b60fb86d686e924189257be9bdd" and "e078f5c0626d078c18c5eeb65ee5abc78a2caa41" have entirely different histories.
		
	
	
		
			5cc93d3107
			...
			e078f5c062
		
	
		
							
								
								
									
										61
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										61
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -656,19 +656,6 @@ dependencies = [ | |||||||
|  "crossbeam-utils", |  "crossbeam-utils", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "console" |  | ||||||
| version = "0.16.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "2e09ced7ebbccb63b4c65413d821f2e00ce54c5ca4514ddc6b3c892fdbcbc69d" |  | ||||||
| dependencies = [ |  | ||||||
|  "encode_unicode", |  | ||||||
|  "libc", |  | ||||||
|  "once_cell", |  | ||||||
|  "unicode-width 0.2.0", |  | ||||||
|  "windows-sys 0.60.2", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "const-oid" | name = "const-oid" | ||||||
| version = "0.9.6" | version = "0.9.6" | ||||||
| @ -1156,12 +1143,6 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "encode_unicode" |  | ||||||
| version = "1.0.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "encoding_rs" | name = "encoding_rs" | ||||||
| version = "0.8.35" | version = "0.8.35" | ||||||
| @ -1794,6 +1775,7 @@ dependencies = [ | |||||||
|  "helm-wrapper-rs", |  "helm-wrapper-rs", | ||||||
|  "hex", |  "hex", | ||||||
|  "http 1.3.1", |  "http 1.3.1", | ||||||
|  |  "inquire", | ||||||
|  "k3d-rs", |  "k3d-rs", | ||||||
|  "k8s-openapi", |  "k8s-openapi", | ||||||
|  "kube", |  "kube", | ||||||
| @ -1802,7 +1784,6 @@ dependencies = [ | |||||||
|  "libredfish", |  "libredfish", | ||||||
|  "log", |  "log", | ||||||
|  "non-blank-string-rs", |  "non-blank-string-rs", | ||||||
|  "once_cell", |  | ||||||
|  "opnsense-config", |  "opnsense-config", | ||||||
|  "opnsense-config-xml", |  "opnsense-config-xml", | ||||||
|  "pretty_assertions", |  "pretty_assertions", | ||||||
| @ -1835,15 +1816,10 @@ version = "0.1.0" | |||||||
| dependencies = [ | dependencies = [ | ||||||
|  "assert_cmd", |  "assert_cmd", | ||||||
|  "clap", |  "clap", | ||||||
|  "console", |  | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "harmony", |  "harmony", | ||||||
|  "harmony_tui", |  "harmony_tui", | ||||||
|  "indicatif", |  | ||||||
|  "indicatif-log-bridge", |  | ||||||
|  "inquire", |  "inquire", | ||||||
|  "lazy_static", |  | ||||||
|  "log", |  | ||||||
|  "tokio", |  "tokio", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| @ -1854,16 +1830,10 @@ dependencies = [ | |||||||
|  "bollard", |  "bollard", | ||||||
|  "cargo_metadata", |  "cargo_metadata", | ||||||
|  "clap", |  "clap", | ||||||
|  "console", |  | ||||||
|  "current_platform", |  "current_platform", | ||||||
|  "env_logger", |  "env_logger", | ||||||
|  "futures-util", |  "futures-util", | ||||||
|  "harmony_cli", |  | ||||||
|  "indicatif", |  | ||||||
|  "indicatif-log-bridge", |  | ||||||
|  "lazy_static", |  | ||||||
|  "log", |  "log", | ||||||
|  "once_cell", |  | ||||||
|  "serde_json", |  "serde_json", | ||||||
|  "tokio", |  "tokio", | ||||||
| ] | ] | ||||||
| @ -2459,29 +2429,6 @@ dependencies = [ | |||||||
|  "serde", |  "serde", | ||||||
| ] | ] | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "indicatif" |  | ||||||
| version = "0.18.0" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "70a646d946d06bedbbc4cac4c218acf4bbf2d87757a784857025f4d447e4e1cd" |  | ||||||
| dependencies = [ |  | ||||||
|  "console", |  | ||||||
|  "portable-atomic", |  | ||||||
|  "unicode-width 0.2.0", |  | ||||||
|  "unit-prefix", |  | ||||||
|  "web-time", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] |  | ||||||
| name = "indicatif-log-bridge" |  | ||||||
| version = "0.2.3" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "63703cf9069b85dbe6fe26e1c5230d013dee99d3559cd3d02ba39e099ef7ab02" |  | ||||||
| dependencies = [ |  | ||||||
|  "indicatif", |  | ||||||
|  "log", |  | ||||||
| ] |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "indoc" | name = "indoc" | ||||||
| version = "2.0.6" | version = "2.0.6" | ||||||
| @ -5314,12 +5261,6 @@ version = "0.2.6" | |||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||||
| checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" | ||||||
| 
 | 
 | ||||||
| [[package]] |  | ||||||
| name = "unit-prefix" |  | ||||||
| version = "0.5.1" |  | ||||||
| source = "registry+https://github.com/rust-lang/crates.io-index" |  | ||||||
| checksum = "323402cff2dd658f39ca17c789b502021b3f18707c91cdf22e3838e1b4023817" |  | ||||||
| 
 |  | ||||||
| [[package]] | [[package]] | ||||||
| name = "universal-hash" | name = "universal-hash" | ||||||
| version = "0.5.1" | version = "0.5.1" | ||||||
|  | |||||||
							
								
								
									
										49
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										49
									
								
								README.md
									
									
									
									
									
								
							| @ -1,6 +1,5 @@ | |||||||
| # Harmony : Open-source infrastructure orchestration that treats your platform like first-class code | # Harmony : Open-source infrastructure orchestration that treats your platform like first-class code. | ||||||
| 
 | *By [NationTech](https://nationtech.io)* | ||||||
| _By [NationTech](https://nationtech.io)_ |  | ||||||
| 
 | 
 | ||||||
| [](https://git.nationtech.io/nationtech/harmony) | [](https://git.nationtech.io/nationtech/harmony) | ||||||
| [](LICENSE) | [](LICENSE) | ||||||
| @ -25,7 +24,7 @@ From a **developer laptop** to a **global production cluster**, a single **sourc | |||||||
| Infrastructure is essential, but it shouldn’t be your core business. Harmony is built on three guiding principles that make modern platforms reliable, repeatable, and easy to reason about. | Infrastructure is essential, but it shouldn’t be your core business. Harmony is built on three guiding principles that make modern platforms reliable, repeatable, and easy to reason about. | ||||||
| 
 | 
 | ||||||
| | Principle | What it means for you | | | Principle | What it means for you | | ||||||
| | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | |-----------|-----------------------| | ||||||
| | **Infrastructure as Resilient Code** | Replace sprawling YAML and bash scripts with type-safe Rust. Test, refactor, and version your platform just like application code. | | | **Infrastructure as Resilient Code** | Replace sprawling YAML and bash scripts with type-safe Rust. Test, refactor, and version your platform just like application code. | | ||||||
| | **Prove It Works — Before You Deploy** | Harmony uses the compiler to verify that your application’s needs match the target environment’s capabilities at **compile-time**, eliminating an entire class of runtime outages. | | | **Prove It Works — Before You Deploy** | Harmony uses the compiler to verify that your application’s needs match the target environment’s capabilities at **compile-time**, eliminating an entire class of runtime outages. | | ||||||
| | **One Unified Model** | Software and infrastructure are a single system. Harmony models them together, enabling deep automation—from bare-metal servers to Kubernetes workloads—with zero context switching. | | | **One Unified Model** | Software and infrastructure are a single system. Harmony models them together, enabling deep automation—from bare-metal servers to Kubernetes workloads—with zero context switching. | | ||||||
| @ -64,20 +63,22 @@ async fn main() { | |||||||
|         }, |         }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     // 2. Enhance with extra scores (monitoring, CI/CD, …) |     // 2. Pick where it should run | ||||||
|  |     let mut maestro = Maestro::<K8sAnywhereTopology>::initialize( | ||||||
|  |         Inventory::autoload(),                // auto-detect hardware / kube-config | ||||||
|  |         K8sAnywhereTopology::from_env(),      // local k3d, CI, staging, prod… | ||||||
|  |     ) | ||||||
|  |     .await | ||||||
|  |     .unwrap(); | ||||||
|  | 
 | ||||||
|  |     // 3. Enhance with extra scores (monitoring, CI/CD, …) | ||||||
|     let mut monitoring = MonitoringAlertingStackScore::new(); |     let mut monitoring = MonitoringAlertingStackScore::new(); | ||||||
|     monitoring.namespace = Some(lamp_stack.config.namespace.clone()); |     monitoring.namespace = Some(lamp_stack.config.namespace.clone()); | ||||||
| 
 | 
 | ||||||
|     // 3. Run your scores on the desired topology & inventory |     maestro.register_all(vec![Box::new(lamp_stack), Box::new(monitoring)]); | ||||||
|     harmony_cli::run( | 
 | ||||||
|         Inventory::autoload(),                // auto-detect hardware / kube-config |     // 4. Launch an interactive CLI / TUI | ||||||
|         K8sAnywhereTopology::from_env(),      // local k3d, CI, staging, prod… |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
|         vec![ |  | ||||||
|           Box::new(lamp_stack), |  | ||||||
|           Box::new(monitoring) |  | ||||||
|         ], |  | ||||||
|         None |  | ||||||
|     ).await.unwrap(); |  | ||||||
| } | } | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| @ -94,10 +95,10 @@ Harmony analyses the code, shows an execution plan in a TUI, and applies it once | |||||||
| ## 3 · Core Concepts | ## 3 · Core Concepts | ||||||
| 
 | 
 | ||||||
| | Term | One-liner | | | Term | One-liner | | ||||||
| | ---------------- | ---------------------------------------------------------------------------------------------------- | | |------|-----------| | ||||||
| | **Score<T>** | Declarative description of the desired state (e.g., `LAMPScore`). | | | **Score<T>** | Declarative description of the desired state (e.g., `LAMPScore`). | | ||||||
| | **Interpret<T>** | Imperative logic that realises a `Score` on a specific environment. | | | **Interpret<T>** | Imperative logic that realises a `Score` on a specific environment. | | ||||||
| | **Topology**     | An environment (local k3d, AWS, bare-metal) exposing verified _Capabilities_ (Kubernetes, DNS, …).   | | | **Topology** | An environment (local k3d, AWS, bare-metal) exposing verified *Capabilities* (Kubernetes, DNS, …). | | ||||||
| | **Maestro** | Orchestrator that compiles Scores + Topology, ensuring all capabilities line up **at compile-time**. | | | **Maestro** | Orchestrator that compiles Scores + Topology, ensuring all capabilities line up **at compile-time**. | | ||||||
| | **Inventory** | Optional catalogue of physical assets for bare-metal and edge deployments. | | | **Inventory** | Optional catalogue of physical assets for bare-metal and edge deployments. | | ||||||
| 
 | 
 | ||||||
| @ -111,9 +112,9 @@ A visual overview is in the diagram below. | |||||||
| 
 | 
 | ||||||
| Prerequisites: | Prerequisites: | ||||||
| 
 | 
 | ||||||
| - Rust | * Rust  | ||||||
| - Docker (if you deploy locally) | * Docker (if you deploy locally) | ||||||
| - `kubectl` / `helm` for Kubernetes-based topologies | * `kubectl` / `helm` for Kubernetes-based topologies | ||||||
| 
 | 
 | ||||||
| ```bash | ```bash | ||||||
| git clone https://git.nationtech.io/nationtech/harmony | git clone https://git.nationtech.io/nationtech/harmony | ||||||
| @ -125,15 +126,15 @@ cargo build --release          # builds the CLI, TUI and libraries | |||||||
| 
 | 
 | ||||||
| ## 5 · Learning More | ## 5 · Learning More | ||||||
| 
 | 
 | ||||||
| - **Architectural Decision Records** – dive into the rationale | * **Architectural Decision Records** – dive into the rationale   | ||||||
|   - [ADR-001 · Why Rust](adr/001-rust.md)   |   - [ADR-001 · Why Rust](adr/001-rust.md)   | ||||||
|   - [ADR-003 · Infrastructure Abstractions](adr/003-infrastructure-abstractions.md)   |   - [ADR-003 · Infrastructure Abstractions](adr/003-infrastructure-abstractions.md)   | ||||||
|   - [ADR-006 · Secret Management](adr/006-secret-management.md)   |   - [ADR-006 · Secret Management](adr/006-secret-management.md)   | ||||||
|   - [ADR-011 · Multi-Tenant Cluster](adr/011-multi-tenant-cluster.md) |   - [ADR-011 · Multi-Tenant Cluster](adr/011-multi-tenant-cluster.md) | ||||||
| 
 | 
 | ||||||
| - **Extending Harmony** – write new Scores / Interprets, add hardware like OPNsense firewalls, or embed Harmony in your own tooling (`/docs`). | * **Extending Harmony** – write new Scores / Interprets, add hardware like OPNsense firewalls, or embed Harmony in your own tooling (`/docs`). | ||||||
| 
 | 
 | ||||||
| - **Community** – discussions and roadmap live in [GitLab issues](https://git.nationtech.io/nationtech/harmony/-/issues). PRs, ideas, and feedback are welcome! | * **Community** – discussions and roadmap live in [GitLab issues](https://git.nationtech.io/nationtech/harmony/-/issues). PRs, ideas, and feedback are welcome! | ||||||
| 
 | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| @ -147,4 +148,4 @@ See [LICENSE](LICENSE) for the full text. | |||||||
| 
 | 
 | ||||||
| --- | --- | ||||||
| 
 | 
 | ||||||
| _Made with ❤️ & 🦀 by the NationTech and the Harmony community_ | *Made with ❤️ & 🦀 by the NationTech and the Harmony community* | ||||||
|  | |||||||
| @ -19,6 +19,12 @@ use harmony::{ | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|  |     env_logger::init(); | ||||||
|  | 
 | ||||||
|  |     let topology = K8sAnywhereTopology::from_env(); | ||||||
|  |     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|     //TODO there is a bug where the application is deployed into the namespace matching the
 |     //TODO there is a bug where the application is deployed into the namespace matching the
 | ||||||
|     //application name and the tenant is created in the namesapce matching the tenant name
 |     //application name and the tenant is created in the namesapce matching the tenant name
 | ||||||
|     //in order for the application to be deployed in the tenant namespace the application.name and
 |     //in order for the application to be deployed in the tenant namespace the application.name and
 | ||||||
| @ -50,12 +56,6 @@ async fn main() { | |||||||
|         application, |         application, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     harmony_cli::run( |     maestro.register_all(vec![Box::new(tenant), Box::new(app)]); | ||||||
|         Inventory::autoload(), |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
|         K8sAnywhereTopology::from_env(), |  | ||||||
|         vec![Box::new(tenant), Box::new(app)], |  | ||||||
|         None, |  | ||||||
|     ) |  | ||||||
|     .await |  | ||||||
|     .unwrap(); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,21 +1,20 @@ | |||||||
| use harmony::{ | use harmony::{ | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|     modules::dummy::{ErrorScore, PanicScore, SuccessScore}, |     modules::dummy::{ErrorScore, PanicScore, SuccessScore}, | ||||||
|     topology::LocalhostTopology, |     topology::LocalhostTopology, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     harmony_cli::run( |     let inventory = Inventory::autoload(); | ||||||
|         Inventory::autoload(), |     let topology = LocalhostTopology::new(); | ||||||
|         LocalhostTopology::new(), |     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); | ||||||
|         vec![ | 
 | ||||||
|  |     maestro.register_all(vec![ | ||||||
|         Box::new(SuccessScore {}), |         Box::new(SuccessScore {}), | ||||||
|         Box::new(ErrorScore {}), |         Box::new(ErrorScore {}), | ||||||
|         Box::new(PanicScore {}), |         Box::new(PanicScore {}), | ||||||
|         ], |     ]); | ||||||
|         None, |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
|     ) |  | ||||||
|     .await |  | ||||||
|     .unwrap(); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| use harmony::{ | use harmony::{ | ||||||
|     data::Version, |     data::Version, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|     modules::lamp::{LAMPConfig, LAMPScore}, |     modules::lamp::{LAMPConfig, LAMPScore}, | ||||||
|     topology::{K8sAnywhereTopology, Url}, |     topology::{K8sAnywhereTopology, Url}, | ||||||
| }; | }; | ||||||
| @ -42,13 +43,15 @@ async fn main() { | |||||||
|     // K8sAnywhereTopology as it is the most automatic one that enables you to easily deploy
 |     // 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
 |     // locally, to development environment from a CI, to staging, and to production with settings
 | ||||||
|     // that automatically adapt to each environment grade.
 |     // that automatically adapt to each environment grade.
 | ||||||
|     harmony_cli::run( |     let mut maestro = Maestro::<K8sAnywhereTopology>::initialize( | ||||||
|         Inventory::autoload(), |         Inventory::autoload(), | ||||||
|         K8sAnywhereTopology::from_env(), |         K8sAnywhereTopology::from_env(), | ||||||
|         vec![Box::new(lamp_stack)], |  | ||||||
|         None, |  | ||||||
|     ) |     ) | ||||||
|     .await |     .await | ||||||
|     .unwrap(); |     .unwrap(); | ||||||
|  | 
 | ||||||
|  |     maestro.register_all(vec![Box::new(lamp_stack)]); | ||||||
|  |     // 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.
 | // That's it, end of the infra as code.
 | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ use std::collections::HashMap; | |||||||
| 
 | 
 | ||||||
| use harmony::{ | use harmony::{ | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|     modules::{ |     modules::{ | ||||||
|         monitoring::{ |         monitoring::{ | ||||||
|             alert_channel::discord_alert_channel::DiscordWebhook, |             alert_channel::discord_alert_channel::DiscordWebhook, | ||||||
| @ -73,13 +74,13 @@ async fn main() { | |||||||
|         rules: vec![Box::new(additional_rules), Box::new(additional_rules2)], |         rules: vec![Box::new(additional_rules), Box::new(additional_rules2)], | ||||||
|         service_monitors: vec![service_monitor], |         service_monitors: vec![service_monitor], | ||||||
|     }; |     }; | ||||||
| 
 |     let mut maestro = Maestro::<K8sAnywhereTopology>::initialize( | ||||||
|     harmony_cli::run( |  | ||||||
|         Inventory::autoload(), |         Inventory::autoload(), | ||||||
|         K8sAnywhereTopology::from_env(), |         K8sAnywhereTopology::from_env(), | ||||||
|         vec![Box::new(alerting_score)], |  | ||||||
|         None, |  | ||||||
|     ) |     ) | ||||||
|     .await |     .await | ||||||
|     .unwrap(); |     .unwrap(); | ||||||
|  | 
 | ||||||
|  |     maestro.register_all(vec![Box::new(alerting_score)]); | ||||||
|  |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ use std::collections::HashMap; | |||||||
| use harmony::{ | use harmony::{ | ||||||
|     data::Id, |     data::Id, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|     modules::{ |     modules::{ | ||||||
|         monitoring::{ |         monitoring::{ | ||||||
|             alert_channel::discord_alert_channel::DiscordWebhook, |             alert_channel::discord_alert_channel::DiscordWebhook, | ||||||
| @ -77,13 +78,13 @@ async fn main() { | |||||||
|         rules: vec![Box::new(additional_rules)], |         rules: vec![Box::new(additional_rules)], | ||||||
|         service_monitors: vec![service_monitor], |         service_monitors: vec![service_monitor], | ||||||
|     }; |     }; | ||||||
| 
 |     let mut maestro = Maestro::<K8sAnywhereTopology>::initialize( | ||||||
|     harmony_cli::run( |  | ||||||
|         Inventory::autoload(), |         Inventory::autoload(), | ||||||
|         K8sAnywhereTopology::from_env(), |         K8sAnywhereTopology::from_env(), | ||||||
|         vec![Box::new(tenant), Box::new(alerting_score)], |  | ||||||
|         None, |  | ||||||
|     ) |     ) | ||||||
|     .await |     .await | ||||||
|     .unwrap(); |     .unwrap(); | ||||||
|  | 
 | ||||||
|  |     maestro.register_all(vec![Box::new(tenant), Box::new(alerting_score)]); | ||||||
|  |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,18 +1,20 @@ | |||||||
| use harmony::{ | use harmony::{ | ||||||
|     inventory::Inventory, modules::monitoring::ntfy::ntfy::NtfyScore, topology::K8sAnywhereTopology, |     inventory::Inventory, maestro::Maestro, modules::monitoring::ntfy::ntfy::NtfyScore, | ||||||
|  |     topology::K8sAnywhereTopology, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     harmony_cli::run( |     let mut maestro = Maestro::<K8sAnywhereTopology>::initialize( | ||||||
|         Inventory::autoload(), |         Inventory::autoload(), | ||||||
|         K8sAnywhereTopology::from_env(), |         K8sAnywhereTopology::from_env(), | ||||||
|         vec![Box::new(NtfyScore { |  | ||||||
|             namespace: "monitoring".to_string(), |  | ||||||
|             host: "localhost".to_string(), |  | ||||||
|         })], |  | ||||||
|         None, |  | ||||||
|     ) |     ) | ||||||
|     .await |     .await | ||||||
|     .unwrap(); |     .unwrap(); | ||||||
|  | 
 | ||||||
|  |     maestro.register_all(vec![Box::new(NtfyScore { | ||||||
|  |         namespace: "monitoring".to_string(), | ||||||
|  |         host: "localhost".to_string(), | ||||||
|  |     })]); | ||||||
|  |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ use std::{path::PathBuf, sync::Arc}; | |||||||
| 
 | 
 | ||||||
| use harmony::{ | use harmony::{ | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|     modules::{ |     modules::{ | ||||||
|         application::{ |         application::{ | ||||||
|             ApplicationScore, RustWebFramework, RustWebapp, |             ApplicationScore, RustWebFramework, RustWebapp, | ||||||
| @ -16,6 +17,13 @@ use harmony::{ | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|  |     env_logger::init(); | ||||||
|  | 
 | ||||||
|  |     let topology = K8sAnywhereTopology::from_env(); | ||||||
|  |     let mut maestro = Maestro::initialize(Inventory::autoload(), topology) | ||||||
|  |         .await | ||||||
|  |         .unwrap(); | ||||||
|  | 
 | ||||||
|     let application = Arc::new(RustWebapp { |     let application = Arc::new(RustWebapp { | ||||||
|         name: "harmony-example-rust-webapp".to_string(), |         name: "harmony-example-rust-webapp".to_string(), | ||||||
|         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), |         domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()), | ||||||
| @ -47,12 +55,6 @@ async fn main() { | |||||||
|         application, |         application, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     harmony_cli::run( |     maestro.register_all(vec![Box::new(app)]); | ||||||
|         Inventory::autoload(), |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
|         K8sAnywhereTopology::from_env(), |  | ||||||
|         vec![Box::new(app)], |  | ||||||
|         None, |  | ||||||
|     ) |  | ||||||
|     .await |  | ||||||
|     .unwrap(); |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| use harmony::{ | use harmony::{ | ||||||
|     data::Id, |     data::Id, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|     modules::tenant::TenantScore, |     modules::tenant::TenantScore, | ||||||
|     topology::{K8sAnywhereTopology, tenant::TenantConfig}, |     topology::{K8sAnywhereTopology, tenant::TenantConfig}, | ||||||
| }; | }; | ||||||
| @ -15,14 +16,15 @@ async fn main() { | |||||||
|         }, |         }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     harmony_cli::run( |     let mut maestro = Maestro::<K8sAnywhereTopology>::initialize( | ||||||
|         Inventory::autoload(), |         Inventory::autoload(), | ||||||
|         K8sAnywhereTopology::from_env(), |         K8sAnywhereTopology::from_env(), | ||||||
|         vec![Box::new(tenant)], |  | ||||||
|         None, |  | ||||||
|     ) |     ) | ||||||
|     .await |     .await | ||||||
|     .unwrap(); |     .unwrap(); | ||||||
|  | 
 | ||||||
|  |     maestro.register_all(vec![Box::new(tenant)]); | ||||||
|  |     harmony_cli::init(maestro, None).await.unwrap(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO write tests
 | // TODO write tests
 | ||||||
|  | |||||||
| @ -32,6 +32,7 @@ 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 | ||||||
| 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" | ||||||
| k3d-rs = { path = "../k3d" } | k3d-rs = { path = "../k3d" } | ||||||
| @ -63,7 +64,6 @@ kube-derive = "1.1.0" | |||||||
| bollard.workspace = true | bollard.workspace = true | ||||||
| tar.workspace = true | tar.workspace = true | ||||||
| base64.workspace = true | base64.workspace = true | ||||||
| once_cell = "1.21.3" |  | ||||||
| 
 | 
 | ||||||
| [dev-dependencies] | [dev-dependencies] | ||||||
| pretty_assertions.workspace = true | pretty_assertions.workspace = true | ||||||
|  | |||||||
| @ -1,63 +0,0 @@ | |||||||
| use log::debug; |  | ||||||
| use once_cell::sync::Lazy; |  | ||||||
| use tokio::sync::broadcast; |  | ||||||
| 
 |  | ||||||
| use super::interpret::{InterpretError, Outcome}; |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub enum HarmonyEvent { |  | ||||||
|     HarmonyStarted, |  | ||||||
|     PrepareTopologyStarted { |  | ||||||
|         topology: String, |  | ||||||
|     }, |  | ||||||
|     TopologyPrepared { |  | ||||||
|         topology: String, |  | ||||||
|         outcome: Outcome, |  | ||||||
|     }, |  | ||||||
|     InterpretExecutionStarted { |  | ||||||
|         topology: String, |  | ||||||
|         interpret: String, |  | ||||||
|         message: String, |  | ||||||
|     }, |  | ||||||
|     InterpretExecutionFinished { |  | ||||||
|         topology: String, |  | ||||||
|         interpret: String, |  | ||||||
|         outcome: Result<Outcome, InterpretError>, |  | ||||||
|     }, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static HARMONY_EVENT_BUS: Lazy<broadcast::Sender<HarmonyEvent>> = Lazy::new(|| { |  | ||||||
|     // TODO: Adjust channel capacity
 |  | ||||||
|     let (tx, _rx) = broadcast::channel(100); |  | ||||||
|     tx |  | ||||||
| }); |  | ||||||
| 
 |  | ||||||
| pub fn instrument(event: HarmonyEvent) -> Result<(), &'static str> { |  | ||||||
|     match HARMONY_EVENT_BUS.send(event) { |  | ||||||
|         Ok(_) => Ok(()), |  | ||||||
|         Err(_) => Err("send error: no subscribers"), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub async fn subscribe<F, Fut>(name: &str, mut handler: F) |  | ||||||
| where |  | ||||||
|     F: FnMut(HarmonyEvent) -> Fut + Send + 'static, |  | ||||||
|     Fut: Future<Output = bool> + Send, |  | ||||||
| { |  | ||||||
|     let mut rx = HARMONY_EVENT_BUS.subscribe(); |  | ||||||
|     debug!("[{name}] Service started. Listening for events..."); |  | ||||||
|     loop { |  | ||||||
|         match rx.recv().await { |  | ||||||
|             Ok(event) => { |  | ||||||
|                 if !handler(event).await { |  | ||||||
|                     debug!("[{name}] Handler requested exit."); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Err(broadcast::error::RecvError::Lagged(n)) => { |  | ||||||
|                 debug!("[{name}] Lagged behind by {n} messages."); |  | ||||||
|             } |  | ||||||
|             Err(_) => break, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -1,8 +1,6 @@ | |||||||
| use std::sync::{Arc, Mutex, RwLock}; | use std::sync::{Arc, Mutex, RwLock}; | ||||||
| 
 | 
 | ||||||
| use log::{debug, warn}; | use log::{info, warn}; | ||||||
| 
 |  | ||||||
| use crate::instrumentation::{self, HarmonyEvent}; |  | ||||||
| 
 | 
 | ||||||
| use super::{ | use super::{ | ||||||
|     interpret::{InterpretError, InterpretStatus, Outcome}, |     interpret::{InterpretError, InterpretStatus, Outcome}, | ||||||
| @ -42,18 +40,13 @@ impl<T: Topology> Maestro<T> { | |||||||
|     /// Ensures the associated Topology is ready for operations.
 |     /// Ensures the associated Topology is ready for operations.
 | ||||||
|     /// Delegates the readiness check and potential setup actions to the Topology.
 |     /// Delegates the readiness check and potential setup actions to the Topology.
 | ||||||
|     pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> { |     pub async fn prepare_topology(&self) -> Result<Outcome, InterpretError> { | ||||||
|         instrumentation::instrument(HarmonyEvent::PrepareTopologyStarted { |         info!("Ensuring topology '{}' is ready...", self.topology.name()); | ||||||
|             topology: self.topology.name().to_string(), |  | ||||||
|         }) |  | ||||||
|         .unwrap(); |  | ||||||
| 
 |  | ||||||
|         let outcome = self.topology.ensure_ready().await?; |         let outcome = self.topology.ensure_ready().await?; | ||||||
| 
 |         info!( | ||||||
|         instrumentation::instrument(HarmonyEvent::TopologyPrepared { |             "Topology '{}' readiness check complete: {}", | ||||||
|             topology: self.topology.name().to_string(), |             self.topology.name(), | ||||||
|             outcome: outcome.clone(), |             outcome.status | ||||||
|         }) |         ); | ||||||
|         .unwrap(); |  | ||||||
| 
 | 
 | ||||||
|         self.topology_preparation_result |         self.topology_preparation_result | ||||||
|             .lock() |             .lock() | ||||||
| @ -87,11 +80,11 @@ impl<T: Topology> Maestro<T> { | |||||||
|                 self.topology.name(), |                 self.topology.name(), | ||||||
|             ); |             ); | ||||||
|         } |         } | ||||||
|         debug!("Running score {score:?}"); |         info!("Running score {score:?}"); | ||||||
|         let interpret = score.create_interpret(); |         let interpret = score.create_interpret(); | ||||||
|         debug!("Launching interpret {interpret:?}"); |         info!("Launching interpret {interpret:?}"); | ||||||
|         let result = interpret.execute(&self.inventory, &self.topology).await; |         let result = interpret.execute(&self.inventory, &self.topology).await; | ||||||
|         debug!("Got result {result:?}"); |         info!("Got result {result:?}"); | ||||||
|         result |         result | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -3,7 +3,6 @@ pub mod data; | |||||||
| pub mod executors; | pub mod executors; | ||||||
| pub mod filter; | pub mod filter; | ||||||
| pub mod hardware; | pub mod hardware; | ||||||
| pub mod instrumentation; |  | ||||||
| pub mod interpret; | pub mod interpret; | ||||||
| pub mod inventory; | pub mod inventory; | ||||||
| pub mod maestro; | pub mod maestro; | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| use std::{process::Command, sync::Arc}; | use std::{process::Command, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
|  | use inquire::Confirm; | ||||||
| use log::{debug, info, warn}; | use log::{debug, info, warn}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use tokio::sync::OnceCell; | use tokio::sync::OnceCell; | ||||||
| @ -9,6 +10,7 @@ use crate::{ | |||||||
|     executors::ExecutorError, |     executors::ExecutorError, | ||||||
|     interpret::{InterpretError, InterpretStatus, Outcome}, |     interpret::{InterpretError, InterpretStatus, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|  |     maestro::Maestro, | ||||||
|     modules::{ |     modules::{ | ||||||
|         k3d::K3DInstallationScore, |         k3d::K3DInstallationScore, | ||||||
|         monitoring::kube_prometheus::crd::{ |         monitoring::kube_prometheus::crd::{ | ||||||
| @ -21,6 +23,7 @@ use crate::{ | |||||||
|         }, |         }, | ||||||
|     }, |     }, | ||||||
|     score::Score, |     score::Score, | ||||||
|  |     topology::LocalhostTopology, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| use super::{ | use super::{ | ||||||
| @ -140,8 +143,9 @@ impl K8sAnywhereTopology { | |||||||
|             return Err("Failed to run 'helm -version'".to_string()); |             return Err("Failed to run 'helm -version'".to_string()); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         // Print the version output
 | ||||||
|         let version_output = String::from_utf8_lossy(&version_result.stdout); |         let version_output = String::from_utf8_lossy(&version_result.stdout); | ||||||
|         debug!("Helm version: {}", version_output.trim()); |         println!("Helm version: {}", version_output.trim()); | ||||||
| 
 | 
 | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| @ -159,10 +163,9 @@ impl K8sAnywhereTopology { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn try_install_k3d(&self) -> Result<(), InterpretError> { |     async fn try_install_k3d(&self) -> Result<(), InterpretError> { | ||||||
|         self.get_k3d_installation_score() |         let maestro = Maestro::initialize(Inventory::autoload(), LocalhostTopology::new()).await?; | ||||||
|             .create_interpret() |         let k3d_score = self.get_k3d_installation_score(); | ||||||
|             .execute(&Inventory::empty(), self) |         maestro.interpret(Box::new(k3d_score)).await?; | ||||||
|             .await?; |  | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -172,7 +175,7 @@ impl K8sAnywhereTopology { | |||||||
|         // TODO this deserves some refactoring, it is becoming a bit hard to figure out
 |         // TODO this deserves some refactoring, it is becoming a bit hard to figure out
 | ||||||
|         // be careful when making modifications here
 |         // be careful when making modifications here
 | ||||||
|         if k8s_anywhere_config.use_local_k3d { |         if k8s_anywhere_config.use_local_k3d { | ||||||
|             debug!("Using local k3d cluster because of use_local_k3d set to true"); |             info!("Using local k3d cluster because of use_local_k3d set to true"); | ||||||
|         } else { |         } else { | ||||||
|             if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { |             if let Some(kubeconfig) = &k8s_anywhere_config.kubeconfig { | ||||||
|                 debug!("Loading kubeconfig {kubeconfig}"); |                 debug!("Loading kubeconfig {kubeconfig}"); | ||||||
| @ -204,13 +207,22 @@ impl K8sAnywhereTopology { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if !k8s_anywhere_config.autoinstall { |         if !k8s_anywhere_config.autoinstall { | ||||||
|  |             debug!("Autoinstall confirmation prompt"); | ||||||
|  |             let confirmation = Confirm::new( "Harmony autoinstallation is not activated, do you wish to launch autoinstallation? : ") | ||||||
|  |                 .with_default(false) | ||||||
|  |                 .prompt() | ||||||
|  |                 .expect("Unexpected prompt error"); | ||||||
|  |             debug!("Autoinstall confirmation {confirmation}"); | ||||||
|  | 
 | ||||||
|  |             if !confirmation { | ||||||
|                 warn!( |                 warn!( | ||||||
|                     "Installation cancelled, K8sAnywhere could not initialize a valid Kubernetes client" |                     "Installation cancelled, K8sAnywhere could not initialize a valid Kubernetes client" | ||||||
|                 ); |                 ); | ||||||
|                 return Ok(None); |                 return Ok(None); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         debug!("Starting K8sAnywhere installation"); |         info!("Starting K8sAnywhere installation"); | ||||||
|         self.try_install_k3d().await?; |         self.try_install_k3d().await?; | ||||||
|         let k3d_score = self.get_k3d_installation_score(); |         let k3d_score = self.get_k3d_installation_score(); | ||||||
|         // I feel like having to rely on the k3d_rs crate here is a smell
 |         // I feel like having to rely on the k3d_rs crate here is a smell
 | ||||||
| @ -223,7 +235,7 @@ impl K8sAnywhereTopology { | |||||||
|             Ok(client) => K8sState { |             Ok(client) => K8sState { | ||||||
|                 client: Arc::new(K8sClient::new(client)), |                 client: Arc::new(K8sClient::new(client)), | ||||||
|                 source: K8sSource::LocalK3d, |                 source: K8sSource::LocalK3d, | ||||||
|                 message: "K8s client ready".to_string(), |                 message: "Successfully installed K3D cluster and acquired client".to_string(), | ||||||
|             }, |             }, | ||||||
|             Err(_) => todo!(), |             Err(_) => todo!(), | ||||||
|         }; |         }; | ||||||
| @ -317,7 +329,7 @@ pub struct K8sAnywhereConfig { | |||||||
|     ///
 |     ///
 | ||||||
|     /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
 |     /// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Default: true
 |     /// Default: false
 | ||||||
|     pub autoinstall: bool, |     pub autoinstall: bool, | ||||||
| 
 | 
 | ||||||
|     /// Whether to use local k3d cluster.
 |     /// Whether to use local k3d cluster.
 | ||||||
| @ -336,7 +348,7 @@ impl K8sAnywhereConfig { | |||||||
|             use_system_kubeconfig: std::env::var("HARMONY_USE_SYSTEM_KUBECONFIG") |             use_system_kubeconfig: std::env::var("HARMONY_USE_SYSTEM_KUBECONFIG") | ||||||
|                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), |                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), | ||||||
|             autoinstall: std::env::var("HARMONY_AUTOINSTALL") |             autoinstall: std::env::var("HARMONY_AUTOINSTALL") | ||||||
|                 .map_or_else(|_| true, |v| v.parse().ok().unwrap_or(false)), |                 .map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)), | ||||||
|             // TODO harmony_profile should be managed at a more core level than this
 |             // TODO harmony_profile should be managed at a more core level than this
 | ||||||
|             harmony_profile: std::env::var("HARMONY_PROFILE").map_or_else( |             harmony_profile: std::env::var("HARMONY_PROFILE").map_or_else( | ||||||
|                 |_| "dev".to_string(), |                 |_| "dev".to_string(), | ||||||
|  | |||||||
| @ -1,7 +1,7 @@ | |||||||
| use std::{io::Write, process::Command, sync::Arc}; | use std::{io::Write, process::Command, sync::Arc}; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::{debug, error}; | use log::{error, info}; | ||||||
| use serde_yaml::Value; | use serde_yaml::Value; | ||||||
| use tempfile::NamedTempFile; | use tempfile::NamedTempFile; | ||||||
| 
 | 
 | ||||||
| @ -63,7 +63,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|         error!("TODO hardcoded k3d bin path is wrong"); |         error!("TODO hardcoded k3d bin path is wrong"); | ||||||
|         let k3d_bin_path = (*HARMONY_DATA_DIR).join("k3d").join("k3d"); |         let k3d_bin_path = (*HARMONY_DATA_DIR).join("k3d").join("k3d"); | ||||||
|         // --- 1. Import the container image into the k3d cluster ---
 |         // --- 1. Import the container image into the k3d cluster ---
 | ||||||
|         debug!( |         info!( | ||||||
|             "Importing image '{}' into k3d cluster 'harmony'", |             "Importing image '{}' into k3d cluster 'harmony'", | ||||||
|             image_name |             image_name | ||||||
|         ); |         ); | ||||||
| @ -80,7 +80,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // --- 2. Get the kubeconfig for the k3d cluster and write it to a temp file ---
 |         // --- 2. Get the kubeconfig for the k3d cluster and write it to a temp file ---
 | ||||||
|         debug!("Retrieving kubeconfig for k3d cluster 'harmony'"); |         info!("Retrieving kubeconfig for k3d cluster 'harmony'"); | ||||||
|         let kubeconfig_output = Command::new(&k3d_bin_path) |         let kubeconfig_output = Command::new(&k3d_bin_path) | ||||||
|             .args(["kubeconfig", "get", "harmony"]) |             .args(["kubeconfig", "get", "harmony"]) | ||||||
|             .output() |             .output() | ||||||
| @ -101,7 +101,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|         let kubeconfig_path = temp_kubeconfig.path().to_str().unwrap(); |         let kubeconfig_path = temp_kubeconfig.path().to_str().unwrap(); | ||||||
| 
 | 
 | ||||||
|         // --- 3. Install or upgrade the Helm chart in the cluster ---
 |         // --- 3. Install or upgrade the Helm chart in the cluster ---
 | ||||||
|         debug!( |         info!( | ||||||
|             "Deploying Helm chart '{}' to namespace '{}'", |             "Deploying Helm chart '{}' to namespace '{}'", | ||||||
|             chart_url, app_name |             chart_url, app_name | ||||||
|         ); |         ); | ||||||
| @ -131,7 +131,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> { | |||||||
|             )); |             )); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         debug!("Successfully deployed '{}' to local k3d cluster.", app_name); |         info!("Successfully deployed '{}' to local k3d cluster.", app_name); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @ -145,19 +145,24 @@ impl< | |||||||
|     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { |     async fn ensure_installed(&self, topology: &T) -> Result<(), String> { | ||||||
|         let image = self.application.image_name(); |         let image = self.application.image_name(); | ||||||
| 
 | 
 | ||||||
|  |         // TODO
 | ||||||
|  |         error!( | ||||||
|  |             "TODO reverse helm chart packaging and docker image build. I put helm package first for faster iterations" | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|         // TODO Write CI/CD workflow files
 |         // TODO Write CI/CD workflow files
 | ||||||
|         // we can autotedect the CI type using the remote url (default to github action for github
 |         // we can autotedect the CI type using the remote url (default to github action for github
 | ||||||
|         // url, etc..)
 |         // url, etc..)
 | ||||||
|         // Or ask for it when unknown
 |         // Or ask for it when unknown
 | ||||||
| 
 | 
 | ||||||
|         let helm_chart = self.application.build_push_helm_package(&image).await?; |         let helm_chart = self.application.build_push_helm_package(&image).await?; | ||||||
|         debug!("Pushed new helm chart {helm_chart}"); |         info!("Pushed new helm chart {helm_chart}"); | ||||||
| 
 | 
 | ||||||
|         error!("TODO Make building image configurable/skippable if image already exists (prompt)"); |         error!("TODO Make building image configurable/skippable if image already exists (prompt)"); | ||||||
|         let image = self.application.build_push_oci_image().await?; |         let image = self.application.build_push_oci_image().await?; | ||||||
|         debug!("Pushed new docker image {image}"); |         info!("Pushed new docker image {image}"); | ||||||
| 
 | 
 | ||||||
|         debug!("Installing ContinuousDelivery feature"); |         info!("Installing ContinuousDelivery feature"); | ||||||
|         // TODO this is a temporary hack for demo purposes, the deployment target should be driven
 |         // TODO this is a temporary hack for demo purposes, the deployment target should be driven
 | ||||||
|         // by the topology only and we should not have to know how to perform tasks like this for
 |         // by the topology only and we should not have to know how to perform tasks like this for
 | ||||||
|         // which the topology should be responsible.
 |         // which the topology should be responsible.
 | ||||||
| @ -177,7 +182,7 @@ impl< | |||||||
|                     .await?; |                     .await?; | ||||||
|             } |             } | ||||||
|             target => { |             target => { | ||||||
|                 debug!("Deploying to target {target:?}"); |                 info!("Deploying to target {target:?}"); | ||||||
|                 let score = ArgoHelmScore { |                 let score = ArgoHelmScore { | ||||||
|                     namespace: "harmonydemo-staging".to_string(), |                     namespace: "harmonydemo-staging".to_string(), | ||||||
|                     openshift: false, |                     openshift: false, | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ mod rust; | |||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
| 
 | 
 | ||||||
| pub use feature::*; | pub use feature::*; | ||||||
| use log::debug; | use log::info; | ||||||
| pub use oci::*; | pub use oci::*; | ||||||
| pub use rust::*; | pub use rust::*; | ||||||
| 
 | 
 | ||||||
| @ -37,7 +37,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application | |||||||
|         topology: &T, |         topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         let app_name = self.application.name(); |         let app_name = self.application.name(); | ||||||
|         debug!( |         info!( | ||||||
|             "Preparing {} features [{}] for application {app_name}", |             "Preparing {} features [{}] for application {app_name}", | ||||||
|             self.features.len(), |             self.features.len(), | ||||||
|             self.features |             self.features | ||||||
| @ -47,7 +47,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application | |||||||
|                 .join(", ") |                 .join(", ") | ||||||
|         ); |         ); | ||||||
|         for feature in self.features.iter() { |         for feature in self.features.iter() { | ||||||
|             debug!( |             info!( | ||||||
|                 "Installing feature {} for application {app_name}", |                 "Installing feature {} for application {app_name}", | ||||||
|                 feature.name() |                 feature.name() | ||||||
|             ); |             ); | ||||||
|  | |||||||
| @ -10,7 +10,7 @@ use dockerfile_builder::Dockerfile; | |||||||
| use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; | use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR}; | ||||||
| use dockerfile_builder::instruction_builder::CopyBuilder; | use dockerfile_builder::instruction_builder::CopyBuilder; | ||||||
| use futures_util::StreamExt; | use futures_util::StreamExt; | ||||||
| use log::{debug, error, log_enabled}; | use log::{debug, error, info}; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| use tar::Archive; | use tar::Archive; | ||||||
| 
 | 
 | ||||||
| @ -73,19 +73,19 @@ impl Application for RustWebapp { | |||||||
| #[async_trait] | #[async_trait] | ||||||
| impl HelmPackage for RustWebapp { | impl HelmPackage for RustWebapp { | ||||||
|     async fn build_push_helm_package(&self, image_url: &str) -> Result<String, String> { |     async fn build_push_helm_package(&self, image_url: &str) -> Result<String, String> { | ||||||
|         debug!("Starting Helm chart build and push for '{}'", self.name); |         info!("Starting Helm chart build and push for '{}'", self.name); | ||||||
| 
 | 
 | ||||||
|         // 1. Create the Helm chart files on disk.
 |         // 1. Create the Helm chart files on disk.
 | ||||||
|         let chart_dir = self |         let chart_dir = self | ||||||
|             .create_helm_chart_files(image_url) |             .create_helm_chart_files(image_url) | ||||||
|             .map_err(|e| format!("Failed to create Helm chart files: {}", e))?; |             .map_err(|e| format!("Failed to create Helm chart files: {}", e))?; | ||||||
|         debug!("Successfully created Helm chart files in {:?}", chart_dir); |         info!("Successfully created Helm chart files in {:?}", chart_dir); | ||||||
| 
 | 
 | ||||||
|         // 2. Package the chart into a .tgz archive.
 |         // 2. Package the chart into a .tgz archive.
 | ||||||
|         let packaged_chart_path = self |         let packaged_chart_path = self | ||||||
|             .package_helm_chart(&chart_dir) |             .package_helm_chart(&chart_dir) | ||||||
|             .map_err(|e| format!("Failed to package Helm chart: {}", e))?; |             .map_err(|e| format!("Failed to package Helm chart: {}", e))?; | ||||||
|         debug!( |         info!( | ||||||
|             "Successfully packaged Helm chart: {}", |             "Successfully packaged Helm chart: {}", | ||||||
|             packaged_chart_path.to_string_lossy() |             packaged_chart_path.to_string_lossy() | ||||||
|         ); |         ); | ||||||
| @ -94,7 +94,7 @@ impl HelmPackage for RustWebapp { | |||||||
|         let oci_chart_url = self |         let oci_chart_url = self | ||||||
|             .push_helm_chart(&packaged_chart_path) |             .push_helm_chart(&packaged_chart_path) | ||||||
|             .map_err(|e| format!("Failed to push Helm chart: {}", e))?; |             .map_err(|e| format!("Failed to push Helm chart: {}", e))?; | ||||||
|         debug!("Successfully pushed Helm chart to: {}", oci_chart_url); |         info!("Successfully pushed Helm chart to: {}", oci_chart_url); | ||||||
| 
 | 
 | ||||||
|         Ok(oci_chart_url) |         Ok(oci_chart_url) | ||||||
|     } |     } | ||||||
| @ -107,20 +107,20 @@ impl OCICompliant for RustWebapp { | |||||||
|     async fn build_push_oci_image(&self) -> Result<String, String> { |     async fn build_push_oci_image(&self) -> Result<String, String> { | ||||||
|         // This function orchestrates the build and push process.
 |         // This function orchestrates the build and push process.
 | ||||||
|         // It's async to match the trait definition, though the underlying docker commands are blocking.
 |         // It's async to match the trait definition, though the underlying docker commands are blocking.
 | ||||||
|         debug!("Starting OCI image build and push for '{}'", self.name); |         info!("Starting OCI image build and push for '{}'", self.name); | ||||||
| 
 | 
 | ||||||
|         // 1. Build the image by calling the synchronous helper function.
 |         // 1. Build the image by calling the synchronous helper function.
 | ||||||
|         let image_tag = self.image_name(); |         let image_tag = self.image_name(); | ||||||
|         self.build_docker_image(&image_tag) |         self.build_docker_image(&image_tag) | ||||||
|             .await |             .await | ||||||
|             .map_err(|e| format!("Failed to build Docker image: {}", e))?; |             .map_err(|e| format!("Failed to build Docker image: {}", e))?; | ||||||
|         debug!("Successfully built Docker image: {}", image_tag); |         info!("Successfully built Docker image: {}", image_tag); | ||||||
| 
 | 
 | ||||||
|         // 2. Push the image to the registry.
 |         // 2. Push the image to the registry.
 | ||||||
|         self.push_docker_image(&image_tag) |         self.push_docker_image(&image_tag) | ||||||
|             .await |             .await | ||||||
|             .map_err(|e| format!("Failed to push Docker image: {}", e))?; |             .map_err(|e| format!("Failed to push Docker image: {}", e))?; | ||||||
|         debug!("Successfully pushed Docker image to: {}", image_tag); |         info!("Successfully pushed Docker image to: {}", image_tag); | ||||||
| 
 | 
 | ||||||
|         Ok(image_tag) |         Ok(image_tag) | ||||||
|     } |     } | ||||||
| @ -159,17 +159,15 @@ impl RustWebapp { | |||||||
|         &self, |         &self, | ||||||
|         image_name: &str, |         image_name: &str, | ||||||
|     ) -> Result<String, Box<dyn std::error::Error>> { |     ) -> Result<String, Box<dyn std::error::Error>> { | ||||||
|         debug!("Generating Dockerfile for '{}'", self.name); |         info!("Generating Dockerfile for '{}'", self.name); | ||||||
|         let _dockerfile_path = self.build_dockerfile()?; |         let _dockerfile_path = self.build_dockerfile()?; | ||||||
| 
 | 
 | ||||||
|         let docker = Docker::connect_with_socket_defaults().unwrap(); |         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||||
| 
 | 
 | ||||||
|         let quiet = !log_enabled!(log::Level::Debug); |  | ||||||
| 
 |  | ||||||
|         let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default() |         let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default() | ||||||
|             .dockerfile("Dockerfile.harmony") |             .dockerfile("Dockerfile.harmony") | ||||||
|             .t(image_name) |             .t(image_name) | ||||||
|             .q(quiet) |             .q(false) | ||||||
|             .version(bollard::query_parameters::BuilderVersion::BuilderV1) |             .version(bollard::query_parameters::BuilderVersion::BuilderV1) | ||||||
|             .platform("linux/x86_64"); |             .platform("linux/x86_64"); | ||||||
| 
 | 
 | ||||||
| @ -206,7 +204,7 @@ impl RustWebapp { | |||||||
|         &self, |         &self, | ||||||
|         image_tag: &str, |         image_tag: &str, | ||||||
|     ) -> Result<String, Box<dyn std::error::Error>> { |     ) -> Result<String, Box<dyn std::error::Error>> { | ||||||
|         debug!("Pushing docker image {image_tag}"); |         info!("Pushing docker image {image_tag}"); | ||||||
| 
 | 
 | ||||||
|         let docker = Docker::connect_with_socket_defaults().unwrap(); |         let docker = Docker::connect_with_socket_defaults().unwrap(); | ||||||
| 
 | 
 | ||||||
| @ -535,7 +533,7 @@ spec: | |||||||
|         chart_dir: &PathBuf, |         chart_dir: &PathBuf, | ||||||
|     ) -> Result<PathBuf, Box<dyn std::error::Error>> { |     ) -> Result<PathBuf, Box<dyn std::error::Error>> { | ||||||
|         let chart_dirname = chart_dir.file_name().expect("Should find a chart dirname"); |         let chart_dirname = chart_dir.file_name().expect("Should find a chart dirname"); | ||||||
|         debug!( |         info!( | ||||||
|             "Launching `helm package {}` cli with CWD {}", |             "Launching `helm package {}` cli with CWD {}", | ||||||
|             chart_dirname.to_string_lossy(), |             chart_dirname.to_string_lossy(), | ||||||
|             &self |             &self | ||||||
| @ -580,7 +578,7 @@ spec: | |||||||
|         let oci_push_url = format!("oci://{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT); |         let oci_push_url = format!("oci://{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT); | ||||||
|         let oci_pull_url = format!("{oci_push_url}/{}-chart", self.name); |         let oci_pull_url = format!("{oci_push_url}/{}-chart", self.name); | ||||||
| 
 | 
 | ||||||
|         debug!( |         info!( | ||||||
|             "Pushing Helm chart {} to {}", |             "Pushing Helm chart {} to {}", | ||||||
|             packaged_chart_path.to_string_lossy(), |             packaged_chart_path.to_string_lossy(), | ||||||
|             oci_push_url |             oci_push_url | ||||||
|  | |||||||
| @ -1,13 +1,12 @@ | |||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| 
 | 
 | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use log::{debug, info}; | use log::info; | ||||||
| use serde::Serialize; | use serde::Serialize; | ||||||
| 
 | 
 | ||||||
| use crate::{ | use crate::{ | ||||||
|     config::HARMONY_DATA_DIR, |     config::HARMONY_DATA_DIR, | ||||||
|     data::{Id, Version}, |     data::{Id, Version}, | ||||||
|     instrumentation::{self, HarmonyEvent}, |  | ||||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, |     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||||
|     inventory::Inventory, |     inventory::Inventory, | ||||||
|     score::Score, |     score::Score, | ||||||
| @ -51,38 +50,22 @@ impl<T: Topology> Interpret<T> for K3dInstallationInterpret { | |||||||
|     async fn execute( |     async fn execute( | ||||||
|         &self, |         &self, | ||||||
|         _inventory: &Inventory, |         _inventory: &Inventory, | ||||||
|         topology: &T, |         _topology: &T, | ||||||
|     ) -> Result<Outcome, InterpretError> { |     ) -> Result<Outcome, InterpretError> { | ||||||
|         instrumentation::instrument(HarmonyEvent::InterpretExecutionStarted { |  | ||||||
|             topology: topology.name().into(), |  | ||||||
|             interpret: "k3d-installation".into(), |  | ||||||
|             message: "installing k3d...".into(), |  | ||||||
|         }) |  | ||||||
|         .unwrap(); |  | ||||||
| 
 |  | ||||||
|         let k3d = k3d_rs::K3d::new( |         let k3d = k3d_rs::K3d::new( | ||||||
|             self.score.installation_path.clone(), |             self.score.installation_path.clone(), | ||||||
|             Some(self.score.cluster_name.clone()), |             Some(self.score.cluster_name.clone()), | ||||||
|         ); |         ); | ||||||
|         let outcome = match k3d.ensure_installed().await { |         match k3d.ensure_installed().await { | ||||||
|             Ok(_client) => { |             Ok(_client) => { | ||||||
|                 let msg = format!("k3d cluster '{}' installed ", self.score.cluster_name); |                 let msg = format!("k3d cluster {} is installed ", self.score.cluster_name); | ||||||
|                 debug!("{msg}"); |                 info!("{msg}"); | ||||||
|                 Ok(Outcome::success(msg)) |                 Ok(Outcome::success(msg)) | ||||||
|             } |             } | ||||||
|             Err(msg) => Err(InterpretError::new(format!( |             Err(msg) => Err(InterpretError::new(format!( | ||||||
|                 "failed to ensure k3d is installed : {msg}" |                 "K3dInstallationInterpret failed to ensure k3d is installed : {msg}" | ||||||
|             ))), |             ))), | ||||||
|         }; |         } | ||||||
| 
 |  | ||||||
|         instrumentation::instrument(HarmonyEvent::InterpretExecutionFinished { |  | ||||||
|             topology: topology.name().into(), |  | ||||||
|             interpret: "k3d-installation".into(), |  | ||||||
|             outcome: outcome.clone(), |  | ||||||
|         }) |  | ||||||
|         .unwrap(); |  | ||||||
| 
 |  | ||||||
|         outcome |  | ||||||
|     } |     } | ||||||
|     fn get_name(&self) -> InterpretName { |     fn get_name(&self) -> InterpretName { | ||||||
|         InterpretName::K3dInstallation |         InterpretName::K3dInstallation | ||||||
|  | |||||||
| @ -13,11 +13,6 @@ harmony_tui = { path = "../harmony_tui", optional = true } | |||||||
| inquire.workspace = true | inquire.workspace = true | ||||||
| tokio.workspace = true | tokio.workspace = true | ||||||
| env_logger.workspace = true | env_logger.workspace = true | ||||||
| console = "0.16.0" |  | ||||||
| indicatif = "0.18.0" |  | ||||||
| lazy_static = "1.5.0" |  | ||||||
| log.workspace = true |  | ||||||
| indicatif-log-bridge = "0.2.3" |  | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| [features] | [features] | ||||||
|  | |||||||
| @ -1,116 +0,0 @@ | |||||||
| use harmony::instrumentation::{self, HarmonyEvent}; |  | ||||||
| use indicatif::{MultiProgress, ProgressBar}; |  | ||||||
| use indicatif_log_bridge::LogWrapper; |  | ||||||
| use std::{ |  | ||||||
|     collections::{HashMap, hash_map}, |  | ||||||
|     sync::{Arc, Mutex}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use crate::progress; |  | ||||||
| 
 |  | ||||||
| pub fn init() -> tokio::task::JoinHandle<()> { |  | ||||||
|     configure_logger(); |  | ||||||
|     let handle = tokio::spawn(handle_events()); |  | ||||||
| 
 |  | ||||||
|     loop { |  | ||||||
|         if instrumentation::instrument(HarmonyEvent::HarmonyStarted).is_ok() { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     handle |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn configure_logger() { |  | ||||||
|     let logger = |  | ||||||
|         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); |  | ||||||
|     let level = logger.filter(); |  | ||||||
|     let multi = MultiProgress::new(); |  | ||||||
|     LogWrapper::new(multi.clone(), logger).try_init().unwrap(); |  | ||||||
|     log::set_max_level(level); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn handle_events() { |  | ||||||
|     instrumentation::subscribe("Harmony CLI Logger", { |  | ||||||
|         let sections: Arc<Mutex<HashMap<String, MultiProgress>>> = |  | ||||||
|             Arc::new(Mutex::new(HashMap::new())); |  | ||||||
|         let progress_bars: Arc<Mutex<HashMap<String, ProgressBar>>> = |  | ||||||
|             Arc::new(Mutex::new(HashMap::new())); |  | ||||||
| 
 |  | ||||||
|         move |event| { |  | ||||||
|             let sections_clone = Arc::clone(§ions); |  | ||||||
|             let progress_bars_clone = Arc::clone(&progress_bars); |  | ||||||
| 
 |  | ||||||
|             async move { |  | ||||||
|                 let mut sections = sections_clone.lock().unwrap(); |  | ||||||
|                 let mut progress_bars = progress_bars_clone.lock().unwrap(); |  | ||||||
| 
 |  | ||||||
|                 match event { |  | ||||||
|                     HarmonyEvent::HarmonyStarted => {} |  | ||||||
|                     HarmonyEvent::PrepareTopologyStarted { topology: name } => { |  | ||||||
|                         let section = progress::new_section(format!( |  | ||||||
|                             "{} Preparing environment: {name}...", |  | ||||||
|                             crate::theme::EMOJI_TOPOLOGY, |  | ||||||
|                         )); |  | ||||||
|                         (*sections).insert(name, section); |  | ||||||
|                     } |  | ||||||
|                     HarmonyEvent::TopologyPrepared { |  | ||||||
|                         topology: name, |  | ||||||
|                         outcome, |  | ||||||
|                     } => { |  | ||||||
|                         let section = (*sections).get(&name).unwrap(); |  | ||||||
|                         let progress = progress::add_spinner(section, "".into()); |  | ||||||
| 
 |  | ||||||
|                         match outcome.status { |  | ||||||
|                             harmony::interpret::InterpretStatus::SUCCESS => { |  | ||||||
|                                 progress::success(section, Some(progress), outcome.message); |  | ||||||
|                             } |  | ||||||
|                             harmony::interpret::InterpretStatus::FAILURE => { |  | ||||||
|                                 progress::error(section, Some(progress), outcome.message); |  | ||||||
|                             } |  | ||||||
|                             harmony::interpret::InterpretStatus::RUNNING => todo!(), |  | ||||||
|                             harmony::interpret::InterpretStatus::QUEUED => todo!(), |  | ||||||
|                             harmony::interpret::InterpretStatus::BLOCKED => todo!(), |  | ||||||
|                             harmony::interpret::InterpretStatus::NOOP => { |  | ||||||
|                                 progress::skip(section, Some(progress), outcome.message); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                     HarmonyEvent::InterpretExecutionStarted { |  | ||||||
|                         interpret: name, |  | ||||||
|                         topology, |  | ||||||
|                         message, |  | ||||||
|                     } => { |  | ||||||
|                         let section = (*sections).get(&topology).unwrap(); |  | ||||||
|                         let progress_bar = progress::add_spinner(section, message); |  | ||||||
| 
 |  | ||||||
|                         (*progress_bars).insert(name, progress_bar); |  | ||||||
|                     } |  | ||||||
|                     HarmonyEvent::InterpretExecutionFinished { |  | ||||||
|                         topology, |  | ||||||
|                         interpret: name, |  | ||||||
|                         outcome, |  | ||||||
|                     } => { |  | ||||||
|                         let section = (*sections).get(&topology).unwrap(); |  | ||||||
|                         let progress_bar = (*progress_bars).get(&name).cloned(); |  | ||||||
| 
 |  | ||||||
|                         let _ = section.clear(); |  | ||||||
| 
 |  | ||||||
|                         match outcome { |  | ||||||
|                             Ok(outcome) => { |  | ||||||
|                                 progress::success(section, progress_bar, outcome.message); |  | ||||||
|                             } |  | ||||||
|                             Err(err) => { |  | ||||||
|                                 progress::error(section, progress_bar, err.to_string()); |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         (*progress_bars).remove(&name); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 true |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }) |  | ||||||
|     .await; |  | ||||||
| } |  | ||||||
| @ -1,14 +1,8 @@ | |||||||
| use clap::Parser; | use clap::Parser; | ||||||
| use clap::builder::ArgPredicate; | use clap::builder::ArgPredicate; | ||||||
| use harmony::inventory::Inventory; | use harmony; | ||||||
| use harmony::maestro::Maestro; |  | ||||||
| use harmony::{score::Score, topology::Topology}; | use harmony::{score::Score, topology::Topology}; | ||||||
| use inquire::Confirm; | use inquire::Confirm; | ||||||
| use log::debug; |  | ||||||
| 
 |  | ||||||
| pub mod cli_logger; // FIXME: Don't make me pub
 |  | ||||||
| pub mod progress; |  | ||||||
| pub mod theme; |  | ||||||
| 
 | 
 | ||||||
| #[cfg(feature = "tui")] | #[cfg(feature = "tui")] | ||||||
| use harmony_tui; | use harmony_tui; | ||||||
| @ -86,24 +80,7 @@ fn list_scores_with_index<T: Topology>(scores_vec: &Vec<Box<dyn Score<T>>>) -> S | |||||||
|     return display_str; |     return display_str; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub async fn run<T: Topology + Send + Sync + 'static>( | pub async fn init<T: Topology + Send + Sync + 'static>( | ||||||
|     inventory: Inventory, |  | ||||||
|     topology: T, |  | ||||||
|     scores: Vec<Box<dyn Score<T>>>, |  | ||||||
|     args_struct: Option<Args>, |  | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { |  | ||||||
|     let cli_logger_handle = cli_logger::init(); |  | ||||||
| 
 |  | ||||||
|     let mut maestro = Maestro::initialize(inventory, topology).await.unwrap(); |  | ||||||
|     maestro.register_all(scores); |  | ||||||
| 
 |  | ||||||
|     let result = init(maestro, args_struct).await; |  | ||||||
| 
 |  | ||||||
|     let _ = tokio::try_join!(cli_logger_handle); |  | ||||||
|     result |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| async fn init<T: Topology + Send + Sync + 'static>( |  | ||||||
|     maestro: harmony::maestro::Maestro<T>, |     maestro: harmony::maestro::Maestro<T>, | ||||||
|     args_struct: Option<Args>, |     args_struct: Option<Args>, | ||||||
| ) -> Result<(), Box<dyn std::error::Error>> { | ) -> Result<(), Box<dyn std::error::Error>> { | ||||||
| @ -157,7 +134,7 @@ async fn init<T: Topology + Send + Sync + 'static>( | |||||||
| 
 | 
 | ||||||
|     // Run filtered scores
 |     // Run filtered scores
 | ||||||
|     for s in scores_vec { |     for s in scores_vec { | ||||||
|         debug!("Running: {}", s.name()); |         println!("Running: {}", s.name()); | ||||||
|         maestro.interpret(s).await?; |         maestro.interpret(s).await?; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,50 +0,0 @@ | |||||||
| use std::time::Duration; |  | ||||||
| 
 |  | ||||||
| use indicatif::{MultiProgress, ProgressBar}; |  | ||||||
| 
 |  | ||||||
| pub fn new_section(title: String) -> MultiProgress { |  | ||||||
|     let multi_progress = MultiProgress::new(); |  | ||||||
|     let _ = multi_progress.println(title); |  | ||||||
| 
 |  | ||||||
|     multi_progress |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn add_spinner(multi_progress: &MultiProgress, message: String) -> ProgressBar { |  | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |  | ||||||
| 
 |  | ||||||
|     progress.set_style(crate::theme::SPINNER_STYLE.clone()); |  | ||||||
|     progress.set_message(message); |  | ||||||
|     progress.enable_steady_tick(Duration::from_millis(100)); |  | ||||||
| 
 |  | ||||||
|     progress |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn success(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { |  | ||||||
|     if let Some(progress) = progress { |  | ||||||
|         multi_progress.remove(&progress) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |  | ||||||
|     progress.set_style(crate::theme::SUCCESS_SPINNER_STYLE.clone()); |  | ||||||
|     progress.finish_with_message(message); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn error(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { |  | ||||||
|     if let Some(progress) = progress { |  | ||||||
|         multi_progress.remove(&progress) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |  | ||||||
|     progress.set_style(crate::theme::ERROR_SPINNER_STYLE.clone()); |  | ||||||
|     progress.finish_with_message(message); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub fn skip(multi_progress: &MultiProgress, progress: Option<ProgressBar>, message: String) { |  | ||||||
|     if let Some(progress) = progress { |  | ||||||
|         multi_progress.remove(&progress) |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     let progress = multi_progress.add(ProgressBar::new_spinner()); |  | ||||||
|     progress.set_style(crate::theme::SKIP_SPINNER_STYLE.clone()); |  | ||||||
|     progress.finish_with_message(message); |  | ||||||
| } |  | ||||||
| @ -1,26 +0,0 @@ | |||||||
| use console::Emoji; |  | ||||||
| use indicatif::ProgressStyle; |  | ||||||
| use lazy_static::lazy_static; |  | ||||||
| 
 |  | ||||||
| pub static EMOJI_HARMONY: Emoji<'_, '_> = Emoji("🎼", ""); |  | ||||||
| pub static EMOJI_SUCCESS: Emoji<'_, '_> = Emoji("✅", ""); |  | ||||||
| pub static EMOJI_SKIP: Emoji<'_, '_> = Emoji("⏭️", ""); |  | ||||||
| pub static EMOJI_ERROR: Emoji<'_, '_> = Emoji("⚠️", ""); |  | ||||||
| pub static EMOJI_DEPLOY: Emoji<'_, '_> = Emoji("🚀", ""); |  | ||||||
| pub static EMOJI_TOPOLOGY: Emoji<'_, '_> = Emoji("📦", ""); |  | ||||||
| 
 |  | ||||||
| lazy_static! { |  | ||||||
|     pub static ref SPINNER_STYLE: ProgressStyle = ProgressStyle::default_spinner() |  | ||||||
|         .template("    {spinner:.green} {msg}") |  | ||||||
|         .unwrap() |  | ||||||
|         .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]); |  | ||||||
|     pub static ref SUCCESS_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE |  | ||||||
|         .clone() |  | ||||||
|         .tick_strings(&[format!("{}", EMOJI_SUCCESS).as_str()]); |  | ||||||
|     pub static ref SKIP_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE |  | ||||||
|         .clone() |  | ||||||
|         .tick_strings(&[format!("{}", EMOJI_SKIP).as_str()]); |  | ||||||
|     pub static ref ERROR_SPINNER_STYLE: ProgressStyle = SPINNER_STYLE |  | ||||||
|         .clone() |  | ||||||
|         .tick_strings(&[format!("{}", EMOJI_ERROR).as_str()]); |  | ||||||
| } |  | ||||||
| @ -15,9 +15,3 @@ current_platform = "0.2.0" | |||||||
| futures-util = "0.3.31" | futures-util = "0.3.31" | ||||||
| serde_json = "1.0.140" | serde_json = "1.0.140" | ||||||
| cargo_metadata = "0.20.0" | cargo_metadata = "0.20.0" | ||||||
| indicatif = "0.18.0" |  | ||||||
| console = "0.16.0" |  | ||||||
| lazy_static = "1.5.0" |  | ||||||
| once_cell = "1.21.3" |  | ||||||
| harmony_cli = { path = "../harmony_cli" } |  | ||||||
| indicatif-log-bridge = "0.2.3" |  | ||||||
|  | |||||||
| @ -1,112 +0,0 @@ | |||||||
| use indicatif::{MultiProgress, ProgressBar}; |  | ||||||
| use indicatif_log_bridge::LogWrapper; |  | ||||||
| use log::error; |  | ||||||
| use std::{ |  | ||||||
|     collections::HashMap, |  | ||||||
|     sync::{Arc, Mutex}, |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| use crate::instrumentation::{self, HarmonyComposerEvent}; |  | ||||||
| 
 |  | ||||||
| pub fn init() -> tokio::task::JoinHandle<()> { |  | ||||||
|     configure_logger(); |  | ||||||
|     let handle = tokio::spawn(handle_events()); |  | ||||||
| 
 |  | ||||||
|     loop { |  | ||||||
|         if instrumentation::instrument(HarmonyComposerEvent::HarmonyComposerStarted).is_ok() { |  | ||||||
|             break; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     handle |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| fn configure_logger() { |  | ||||||
|     let logger = |  | ||||||
|         env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).build(); |  | ||||||
|     let level = logger.filter(); |  | ||||||
|     let multi = MultiProgress::new(); |  | ||||||
|     LogWrapper::new(multi.clone(), logger).try_init().unwrap(); |  | ||||||
|     log::set_max_level(level); |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub async fn handle_events() { |  | ||||||
|     const PROGRESS_SETUP: &str = "project-initialization"; |  | ||||||
|     const PROGRESS_DEPLOYMENT: &str = "deployment"; |  | ||||||
| 
 |  | ||||||
|     instrumentation::subscribe("Harmony Composer Logger", { |  | ||||||
|         let progresses: Arc<Mutex<HashMap<String, MultiProgress>>> = |  | ||||||
|             Arc::new(Mutex::new(HashMap::new())); |  | ||||||
|         let compilation_progress = Arc::new(Mutex::new(None::<ProgressBar>)); |  | ||||||
| 
 |  | ||||||
|         move |event| { |  | ||||||
|             let progresses_clone = Arc::clone(&progresses); |  | ||||||
|             let compilation_progress_clone = Arc::clone(&compilation_progress); |  | ||||||
| 
 |  | ||||||
|             async move { |  | ||||||
|                 let mut progresses_guard = progresses_clone.lock().unwrap(); |  | ||||||
|                 let mut compilation_progress_guard = compilation_progress_clone.lock().unwrap(); |  | ||||||
| 
 |  | ||||||
|                 match event { |  | ||||||
|                     HarmonyComposerEvent::HarmonyComposerStarted => {} |  | ||||||
|                     HarmonyComposerEvent::ProjectInitializationStarted => { |  | ||||||
|                         let multi_progress = harmony_cli::progress::new_section(format!( |  | ||||||
|                             "{} Initializing Harmony project...", |  | ||||||
|                             harmony_cli::theme::EMOJI_HARMONY, |  | ||||||
|                         )); |  | ||||||
|                         (*progresses_guard).insert(PROGRESS_SETUP.to_string(), multi_progress); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::ProjectInitialized => println!("\n"), |  | ||||||
|                     HarmonyComposerEvent::ProjectCompilationStarted { details } => { |  | ||||||
|                         let initialization_progress = |  | ||||||
|                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); |  | ||||||
|                         let _ = initialization_progress.clear(); |  | ||||||
| 
 |  | ||||||
|                         let progress = |  | ||||||
|                             harmony_cli::progress::add_spinner(initialization_progress, details); |  | ||||||
|                         *compilation_progress_guard = Some(progress); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::ProjectCompiled => { |  | ||||||
|                         let initialization_progress = |  | ||||||
|                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); |  | ||||||
| 
 |  | ||||||
|                         harmony_cli::progress::success( |  | ||||||
|                             initialization_progress, |  | ||||||
|                             (*compilation_progress_guard).take(), |  | ||||||
|                             "project compiled".to_string(), |  | ||||||
|                         ); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::ProjectCompilationFailed { details } => { |  | ||||||
|                         let initialization_progress = |  | ||||||
|                             (*progresses_guard).get(PROGRESS_SETUP).unwrap(); |  | ||||||
| 
 |  | ||||||
|                         harmony_cli::progress::error( |  | ||||||
|                             initialization_progress, |  | ||||||
|                             (*compilation_progress_guard).take(), |  | ||||||
|                             "failed to compile project".to_string(), |  | ||||||
|                         ); |  | ||||||
| 
 |  | ||||||
|                         error!("{details}"); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::DeploymentStarted { target } => { |  | ||||||
|                         let multi_progress = harmony_cli::progress::new_section(format!( |  | ||||||
|                             "{} Starting deployment to {target}...\n\n", |  | ||||||
|                             harmony_cli::theme::EMOJI_DEPLOY |  | ||||||
|                         )); |  | ||||||
|                         (*progresses_guard).insert(PROGRESS_DEPLOYMENT.to_string(), multi_progress); |  | ||||||
|                     } |  | ||||||
|                     HarmonyComposerEvent::DeploymentCompleted { details } => println!("\n"), |  | ||||||
|                     HarmonyComposerEvent::Shutdown => { |  | ||||||
|                         for (_, progresses) in (*progresses_guard).iter() { |  | ||||||
|                             progresses.clear().unwrap(); |  | ||||||
|                         } |  | ||||||
| 
 |  | ||||||
|                         return false; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 true |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }) |  | ||||||
|     .await |  | ||||||
| } |  | ||||||
| @ -1,53 +0,0 @@ | |||||||
| use log::debug; |  | ||||||
| use once_cell::sync::Lazy; |  | ||||||
| use tokio::sync::broadcast; |  | ||||||
| 
 |  | ||||||
| #[derive(Debug, Clone)] |  | ||||||
| pub enum HarmonyComposerEvent { |  | ||||||
|     HarmonyComposerStarted, |  | ||||||
|     ProjectInitializationStarted, |  | ||||||
|     ProjectInitialized, |  | ||||||
|     ProjectCompilationStarted { details: String }, |  | ||||||
|     ProjectCompiled, |  | ||||||
|     ProjectCompilationFailed { details: String }, |  | ||||||
|     DeploymentStarted { target: String }, |  | ||||||
|     DeploymentCompleted { details: String }, |  | ||||||
|     Shutdown, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static HARMONY_COMPOSER_EVENT_BUS: Lazy<broadcast::Sender<HarmonyComposerEvent>> = |  | ||||||
|     Lazy::new(|| { |  | ||||||
|         // TODO: Adjust channel capacity
 |  | ||||||
|         let (tx, _rx) = broadcast::channel(16); |  | ||||||
|         tx |  | ||||||
|     }); |  | ||||||
| 
 |  | ||||||
| pub fn instrument(event: HarmonyComposerEvent) -> Result<(), &'static str> { |  | ||||||
|     match HARMONY_COMPOSER_EVENT_BUS.send(event) { |  | ||||||
|         Ok(_) => Ok(()), |  | ||||||
|         Err(_) => Err("send error: no subscribers"), |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| pub async fn subscribe<F, Fut>(name: &str, mut handler: F) |  | ||||||
| where |  | ||||||
|     F: FnMut(HarmonyComposerEvent) -> Fut + Send + 'static, |  | ||||||
|     Fut: Future<Output = bool> + Send, |  | ||||||
| { |  | ||||||
|     let mut rx = HARMONY_COMPOSER_EVENT_BUS.subscribe(); |  | ||||||
|     debug!("[{name}] Service started. Listening for events..."); |  | ||||||
|     loop { |  | ||||||
|         match rx.recv().await { |  | ||||||
|             Ok(event) => { |  | ||||||
|                 if !handler(event).await { |  | ||||||
|                     debug!("[{name}] Handler requested exit."); |  | ||||||
|                     break; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Err(broadcast::error::RecvError::Lagged(n)) => { |  | ||||||
|                 debug!("[{name}] Lagged behind by {n} messages."); |  | ||||||
|             } |  | ||||||
|             Err(_) => break, |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @ -7,16 +7,12 @@ use bollard::secret::HostConfig; | |||||||
| use cargo_metadata::{Artifact, Message, MetadataCommand}; | use cargo_metadata::{Artifact, Message, MetadataCommand}; | ||||||
| use clap::{Args, Parser, Subcommand}; | use clap::{Args, Parser, Subcommand}; | ||||||
| use futures_util::StreamExt; | use futures_util::StreamExt; | ||||||
| use instrumentation::HarmonyComposerEvent; | use log::info; | ||||||
| use log::{debug, info, log_enabled}; |  | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::path::{Path, PathBuf}; | use std::path::{Path, PathBuf}; | ||||||
| use std::process::{Command, Stdio}; | use std::process::{Command, Stdio}; | ||||||
| use tokio::fs; | use tokio::fs; | ||||||
| 
 | 
 | ||||||
| mod harmony_composer_logger; |  | ||||||
| mod instrumentation; |  | ||||||
| 
 |  | ||||||
| #[derive(Parser)] | #[derive(Parser)] | ||||||
| #[command(version, about, long_about = None, flatten_help = true, propagate_version = true)] | #[command(version, about, long_about = None, flatten_help = true, propagate_version = true)] | ||||||
| struct GlobalArgs { | struct GlobalArgs { | ||||||
| @ -70,15 +66,13 @@ struct AllArgs { | |||||||
| 
 | 
 | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() { | async fn main() { | ||||||
|     let hc_logger_handle = harmony_composer_logger::init(); |     env_logger::init(); | ||||||
|     let cli_args = GlobalArgs::parse(); |     let cli_args = GlobalArgs::parse(); | ||||||
| 
 | 
 | ||||||
|     let harmony_path = Path::new(&cli_args.harmony_path) |     let harmony_path = Path::new(&cli_args.harmony_path) | ||||||
|         .try_exists() |         .try_exists() | ||||||
|         .expect("couldn't check if path exists"); |         .expect("couldn't check if path exists"); | ||||||
| 
 | 
 | ||||||
|     instrumentation::instrument(HarmonyComposerEvent::ProjectInitializationStarted).unwrap(); |  | ||||||
| 
 |  | ||||||
|     let harmony_bin_path: PathBuf = match harmony_path { |     let harmony_bin_path: PathBuf = match harmony_path { | ||||||
|         true => { |         true => { | ||||||
|             compile_harmony( |             compile_harmony( | ||||||
| @ -91,8 +85,6 @@ async fn main() { | |||||||
|         false => todo!("implement autodetect code"), |         false => todo!("implement autodetect code"), | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     instrumentation::instrument(HarmonyComposerEvent::ProjectInitialized).unwrap(); |  | ||||||
| 
 |  | ||||||
|     match cli_args.command { |     match cli_args.command { | ||||||
|         Some(command) => match command { |         Some(command) => match command { | ||||||
|             Commands::Check(args) => { |             Commands::Check(args) => { | ||||||
| @ -124,31 +116,19 @@ async fn main() { | |||||||
|             } |             } | ||||||
|             Commands::Deploy(args) => { |             Commands::Deploy(args) => { | ||||||
|                 let deploy = if args.staging { |                 let deploy = if args.staging { | ||||||
|                     instrumentation::instrument(HarmonyComposerEvent::DeploymentStarted { |  | ||||||
|                         target: "staging".to_string(), |  | ||||||
|                     }) |  | ||||||
|                     .unwrap(); |  | ||||||
|                     todo!("implement staging deployment") |                     todo!("implement staging deployment") | ||||||
|                 } else if args.prod { |                 } else if args.prod { | ||||||
|                     instrumentation::instrument(HarmonyComposerEvent::DeploymentStarted { |  | ||||||
|                         target: "prod".to_string(), |  | ||||||
|                     }) |  | ||||||
|                     .unwrap(); |  | ||||||
|                     todo!("implement prod deployment") |                     todo!("implement prod deployment") | ||||||
|                 } else { |                 } else { | ||||||
|                     instrumentation::instrument(HarmonyComposerEvent::DeploymentStarted { |  | ||||||
|                         target: "dev".to_string(), |  | ||||||
|                     }) |  | ||||||
|                     .unwrap(); |  | ||||||
|                     Command::new(harmony_bin_path).arg("-y").arg("-a").spawn() |                     Command::new(harmony_bin_path).arg("-y").arg("-a").spawn() | ||||||
|                 } |                 } | ||||||
|                 .expect("failed to run harmony deploy"); |                 .expect("failed to run harmony deploy"); | ||||||
| 
 | 
 | ||||||
|                 let deploy_output = deploy.wait_with_output().unwrap(); |                 let deploy_output = deploy.wait_with_output().unwrap(); | ||||||
|                 instrumentation::instrument(HarmonyComposerEvent::DeploymentCompleted { |                 println!( | ||||||
|                     details: String::from_utf8(deploy_output.stdout).unwrap(), |                     "deploy output: {}", | ||||||
|                 }) |                     String::from_utf8(deploy_output.stdout).expect("couldn't parse from utf8") | ||||||
|                 .unwrap(); |                 ); | ||||||
|             } |             } | ||||||
|             Commands::All(_args) => todo!( |             Commands::All(_args) => todo!( | ||||||
|                 "take all previous match arms and turn them into separate functions, and call them all one after the other" |                 "take all previous match arms and turn them into separate functions, and call them all one after the other" | ||||||
| @ -157,10 +137,6 @@ async fn main() { | |||||||
|         }, |         }, | ||||||
|         None => todo!("run interactively, ask for info on CLI"), |         None => todo!("run interactively, ask for info on CLI"), | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     instrumentation::instrument(HarmonyComposerEvent::Shutdown).unwrap(); |  | ||||||
| 
 |  | ||||||
|     let _ = tokio::try_join!(hc_logger_handle); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #[derive(Clone, Debug, clap::ValueEnum)] | #[derive(Clone, Debug, clap::ValueEnum)] | ||||||
| @ -190,32 +166,17 @@ async fn compile_harmony( | |||||||
|         Some(m) => m, |         Some(m) => m, | ||||||
|         None => { |         None => { | ||||||
|             if cargo_exists { |             if cargo_exists { | ||||||
|                 CompileMethod::LocalCargo |                 return compile_cargo(platform, harmony_location).await; | ||||||
|             } else { |             } else { | ||||||
|                 CompileMethod::Docker |                 return compile_docker(platform, harmony_location).await; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     let path = match method { |     match method { | ||||||
|         CompileMethod::LocalCargo => { |         CompileMethod::LocalCargo => return compile_cargo(platform, harmony_location).await, | ||||||
|             instrumentation::instrument(HarmonyComposerEvent::ProjectCompilationStarted { |         CompileMethod::Docker => return compile_docker(platform, harmony_location).await, | ||||||
|                 details: "compiling project with cargo".to_string(), |  | ||||||
|             }) |  | ||||||
|             .unwrap(); |  | ||||||
|             compile_cargo(platform, harmony_location).await |  | ||||||
|         } |  | ||||||
|         CompileMethod::Docker => { |  | ||||||
|             instrumentation::instrument(HarmonyComposerEvent::ProjectCompilationStarted { |  | ||||||
|                 details: "compiling project with docker".to_string(), |  | ||||||
|             }) |  | ||||||
|             .unwrap(); |  | ||||||
|             compile_docker(platform, harmony_location).await |  | ||||||
|         } |  | ||||||
|     }; |     }; | ||||||
| 
 |  | ||||||
|     instrumentation::instrument(HarmonyComposerEvent::ProjectCompiled).unwrap(); |  | ||||||
|     path |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // TODO: make sure this works with cargo workspaces
 | // TODO: make sure this works with cargo workspaces
 | ||||||
| @ -225,12 +186,6 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|         .exec() |         .exec() | ||||||
|         .unwrap(); |         .unwrap(); | ||||||
| 
 | 
 | ||||||
|     let stderr = if log_enabled!(log::Level::Debug) { |  | ||||||
|         Stdio::inherit() |  | ||||||
|     } else { |  | ||||||
|         Stdio::piped() |  | ||||||
|     }; |  | ||||||
| 
 |  | ||||||
|     let mut cargo_build = Command::new("cargo") |     let mut cargo_build = Command::new("cargo") | ||||||
|         .current_dir(&harmony_location) |         .current_dir(&harmony_location) | ||||||
|         .args(vec![ |         .args(vec![ | ||||||
| @ -240,7 +195,6 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|             "--message-format=json-render-diagnostics", |             "--message-format=json-render-diagnostics", | ||||||
|         ]) |         ]) | ||||||
|         .stdout(Stdio::piped()) |         .stdout(Stdio::piped()) | ||||||
|         .stderr(stderr) |  | ||||||
|         .spawn() |         .spawn() | ||||||
|         .expect("run cargo command failed"); |         .expect("run cargo command failed"); | ||||||
| 
 | 
 | ||||||
| @ -256,20 +210,18 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|                         .expect("failed to get root package") |                         .expect("failed to get root package") | ||||||
|                         .manifest_path |                         .manifest_path | ||||||
|                 { |                 { | ||||||
|                     debug!("{:?}", artifact); |                     println!("{:?}", artifact); | ||||||
|                     artifacts.push(artifact); |                     artifacts.push(artifact); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             Message::BuildScriptExecuted(_script) => (), |             Message::BuildScriptExecuted(_script) => (), | ||||||
|             Message::BuildFinished(finished) => { |             Message::BuildFinished(finished) => { | ||||||
|                 debug!("{:?}", finished); |                 println!("{:?}", finished); | ||||||
|             } |             } | ||||||
|             _ => (), // Unknown message
 |             _ => (), // Unknown message
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     cargo_build.wait().expect("run cargo command failed"); |  | ||||||
| 
 |  | ||||||
|     let bin = artifacts |     let bin = artifacts | ||||||
|         .last() |         .last() | ||||||
|         .expect("no binaries built") |         .expect("no binaries built") | ||||||
| @ -285,8 +237,7 @@ async fn compile_cargo(platform: String, harmony_location: String) -> PathBuf { | |||||||
|         bin_out = PathBuf::from(format!("{}/harmony", harmony_location)); |         bin_out = PathBuf::from(format!("{}/harmony", harmony_location)); | ||||||
|         let _copy_res = fs::copy(&bin, &bin_out).await; |         let _copy_res = fs::copy(&bin, &bin_out).await; | ||||||
|     } |     } | ||||||
| 
 |     return bin_out; | ||||||
|     bin_out |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| async fn compile_docker(platform: String, harmony_location: String) -> PathBuf { | async fn compile_docker(platform: String, harmony_location: String) -> PathBuf { | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ mod downloadable_asset; | |||||||
| use downloadable_asset::*; | use downloadable_asset::*; | ||||||
| 
 | 
 | ||||||
| use kube::Client; | use kube::Client; | ||||||
| use log::{debug, warn}; | use log::{debug, info, warn}; | ||||||
| use std::path::PathBuf; | use std::path::PathBuf; | ||||||
| 
 | 
 | ||||||
| const K3D_BIN_FILE_NAME: &str = "k3d"; | const K3D_BIN_FILE_NAME: &str = "k3d"; | ||||||
| @ -90,7 +90,7 @@ impl K3d { | |||||||
|         let latest_release = self.get_latest_release_tag().await.unwrap(); |         let latest_release = self.get_latest_release_tag().await.unwrap(); | ||||||
| 
 | 
 | ||||||
|         let release_binary = self.get_binary_for_current_platform(latest_release).await; |         let release_binary = self.get_binary_for_current_platform(latest_release).await; | ||||||
|         debug!("Foudn K3d binary to install : {release_binary:#?}"); |         info!("Foudn K3d binary to install : {release_binary:#?}"); | ||||||
|         release_binary.download_to_path(self.base_dir.clone()).await |         release_binary.download_to_path(self.base_dir.clone()).await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -175,7 +175,7 @@ impl K3d { | |||||||
|             Err(_) => return Err("Could not get cluster_name, cannot initialize".to_string()), |             Err(_) => return Err("Could not get cluster_name, cannot initialize".to_string()), | ||||||
|         }; |         }; | ||||||
| 
 | 
 | ||||||
|         debug!("Initializing k3d cluster '{}'", cluster_name); |         info!("Initializing k3d cluster '{}'", cluster_name); | ||||||
| 
 | 
 | ||||||
|         self.create_cluster(cluster_name)?; |         self.create_cluster(cluster_name)?; | ||||||
|         self.create_kubernetes_client().await |         self.create_kubernetes_client().await | ||||||
| @ -205,7 +205,7 @@ impl K3d { | |||||||
|     /// - `Err(String)` - Error message if any step failed
 |     /// - `Err(String)` - Error message if any step failed
 | ||||||
|     pub async fn ensure_installed(&self) -> Result<Client, String> { |     pub async fn ensure_installed(&self) -> Result<Client, String> { | ||||||
|         if !self.is_installed() { |         if !self.is_installed() { | ||||||
|             debug!("K3d is not installed, downloading latest release"); |             info!("K3d is not installed, downloading latest release"); | ||||||
|             self.download_latest_release() |             self.download_latest_release() | ||||||
|                 .await |                 .await | ||||||
|                 .map_err(|e| format!("Failed to download k3d: {}", e))?; |                 .map_err(|e| format!("Failed to download k3d: {}", e))?; | ||||||
| @ -216,13 +216,13 @@ impl K3d { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if !self.is_cluster_initialized() { |         if !self.is_cluster_initialized() { | ||||||
|             debug!("Cluster is not initialized, initializing now"); |             info!("Cluster is not initialized, initializing now"); | ||||||
|             return self.initialize_cluster().await; |             return self.initialize_cluster().await; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         self.start_cluster().await?; |         self.start_cluster().await?; | ||||||
| 
 | 
 | ||||||
|         debug!("K3d and cluster are already properly set up"); |         info!("K3d and cluster are already properly set up"); | ||||||
|         self.create_kubernetes_client().await |         self.create_kubernetes_client().await | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -325,12 +325,12 @@ impl K3d { | |||||||
|             return Err(format!("Failed to create cluster: {}", stderr)); |             return Err(format!("Failed to create cluster: {}", stderr)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         debug!("Successfully created k3d cluster '{}'", cluster_name); |         info!("Successfully created k3d cluster '{}'", cluster_name); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async fn create_kubernetes_client(&self) -> Result<Client, String> { |     async fn create_kubernetes_client(&self) -> Result<Client, String> { | ||||||
|         // TODO: Connect the client to the right k3d cluster (see https://git.nationtech.io/NationTech/harmony/issues/92)
 |         warn!("TODO this method is way too dumb, it should make sure that the client is connected to the k3d cluster actually represented by this instance, not just any default  client"); | ||||||
|         Client::try_default() |         Client::try_default() | ||||||
|             .await |             .await | ||||||
|             .map_err(|e| format!("Failed to create Kubernetes client: {}", e)) |             .map_err(|e| format!("Failed to create Kubernetes client: {}", e)) | ||||||
| @ -352,7 +352,7 @@ impl K3d { | |||||||
|             return Err(format!("Failed to start cluster: {}", stderr)); |             return Err(format!("Failed to start cluster: {}", stderr)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         debug!("Successfully started k3d cluster '{}'", cluster_name); |         info!("Successfully started k3d cluster '{}'", cluster_name); | ||||||
|         Ok(()) |         Ok(()) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
		Reference in New Issue
	
	Block a user