From 24d92dd44a0cfe096ea6030448b3a51e5f172c30 Mon Sep 17 00:00:00 2001 From: Lukas Simon Date: Fri, 27 Sep 2024 16:00:38 +0200 Subject: [PATCH] jeweler: Add Deployment trait --- flecs-core/src/jeweler/app.rs | 94 +++++++++++++++++ flecs-core/src/jeweler/deployment.rs | 11 ++ flecs-core/src/jeweler/instance.rs | 147 +++++++++++++++++++++++++++ flecs-core/src/jeweler/mod.rs | 6 ++ flecs-core/src/jeweler/network.rs | 23 +++++ flecs-core/src/jeweler/volume.rs | 27 +++++ flecs-core/src/lib.rs | 2 + 7 files changed, 310 insertions(+) create mode 100644 flecs-core/src/jeweler/app.rs create mode 100644 flecs-core/src/jeweler/deployment.rs create mode 100644 flecs-core/src/jeweler/instance.rs create mode 100644 flecs-core/src/jeweler/mod.rs create mode 100644 flecs-core/src/jeweler/network.rs create mode 100644 flecs-core/src/jeweler/volume.rs diff --git a/flecs-core/src/jeweler/app.rs b/flecs-core/src/jeweler/app.rs new file mode 100644 index 000000000..07a9bedda --- /dev/null +++ b/flecs-core/src/jeweler/app.rs @@ -0,0 +1,94 @@ +pub use super::Result; +use crate::jeweler::deployment::{Deployment, DeploymentId}; +use crate::jeweler::instance::{Instance, InstanceConfig, InstanceId, InstanceStatus}; +use crate::vault::pouch::AppKey; +use anyhow::anyhow; +use async_trait::async_trait; +use flecs_app_manifest::AppManifest; +use std::collections::HashMap; +use std::sync::Arc; + +type AppId = String; +#[async_trait] +pub trait AppDeployment { + async fn install_app(&self, manifest: &AppManifest) -> Result; + async fn uninstall_app(&self, id: AppId) -> Result<()>; + async fn is_app_installed(&self, id: AppId) -> Result; +} +#[derive(Default)] +pub enum AppStatus { + #[default] + None, + Installed, + NotInstalled, +} + +pub struct AppData { + desired: AppStatus, + instances: HashMap, + id: Option, + deployment: Arc, +} + +pub struct App { + key: AppKey, + properties: HashMap, + manifest: Option>, +} + +impl App { + pub async fn install(&mut self) -> Result<()> { + match &self.manifest { + None => Err(anyhow!( + "Can not install {:?}, no manifest present.", + self.key + ))?, + Some(manifest) => { + for data in self.properties.values_mut() { + data.desired = AppStatus::Installed; + // TODO: Installing app in one deployment should not fail the whole install process for all deployments + if let Some(id) = &data.id { + if !data.deployment.is_app_installed(id.clone()).await? { + data.id = Some(data.deployment.install_app(manifest.as_ref()).await?); + } + } + } + } + } + Ok(()) + } + + pub async fn uninstall(&mut self) -> Result<()> { + for data in self.properties.values_mut() { + data.desired = AppStatus::NotInstalled; + // TODO: Uninstalling app in one deployment should not fail the whole uninstall process for all deployments + if let Some(id) = &data.id { + if data.deployment.is_app_installed(id.clone()).await? { + data.deployment.uninstall_app(id.clone()).await?; + data.id = None; + } + } + } + Ok(()) + } + + pub async fn create_instance(&mut self, instance_config: InstanceConfig) -> Result<()> { + for data in self.properties.values_mut() { + // TODO: Creating app in one deployment should not fail the whole creation process for all deployments + let id = data + .deployment + .create_instance(instance_config.clone()) + .await?; + data.instances.insert( + id.clone(), + Instance::new( + id, + instance_config.clone(), + data.deployment.clone(), + InstanceStatus::Created, + ), + ); + } + Ok(()) + } +} diff --git a/flecs-core/src/jeweler/deployment.rs b/flecs-core/src/jeweler/deployment.rs new file mode 100644 index 000000000..95cbbf128 --- /dev/null +++ b/flecs-core/src/jeweler/deployment.rs @@ -0,0 +1,11 @@ +use crate::jeweler::app::AppDeployment; +use crate::jeweler::instance::InstanceDeployment; +use crate::jeweler::network::NetworkDeployment; +use async_trait::async_trait; + +pub type DeploymentId = String; + +#[async_trait] +pub trait Deployment: Send + Sync + AppDeployment + InstanceDeployment + NetworkDeployment { + async fn id(&self) -> DeploymentId; +} diff --git a/flecs-core/src/jeweler/instance.rs b/flecs-core/src/jeweler/instance.rs new file mode 100644 index 000000000..c54ff0fd2 --- /dev/null +++ b/flecs-core/src/jeweler/instance.rs @@ -0,0 +1,147 @@ +use super::Result; +use crate::jeweler::deployment::Deployment; +use crate::jeweler::volume::VolumeDeployment; +use async_trait::async_trait; +use std::collections::HashMap; +use std::path::Path; +use std::sync::Arc; + +#[async_trait] +pub trait InstanceDeployment: VolumeDeployment { + async fn create_instance(&self, config: InstanceConfig) -> Result; + async fn delete_instance(&self, id: InstanceId) -> Result<()>; + async fn start_instance(&self, id: InstanceId) -> Result<()>; + async fn stop_instance(&self, id: InstanceId) -> Result<()>; + async fn ready_instance(&self, id: InstanceId) -> Result<()>; + async fn import_instance(&self, path: &Path) -> Result; + async fn export_instance(&self, id: InstanceId, path: &Path) -> Result<()> { + let config = self.instance_config(id.clone()).await?; + self.export_config(config, path).await?; + self.export_volumes(id, path).await?; + Ok(()) + } + async fn export_config(&self, config: InstanceConfig, path: &Path) -> Result<()>; + async fn instance_status(&self, id: InstanceId) -> Result; + async fn instance_config(&self, id: InstanceId) -> Result; + async fn instance(&self, id: InstanceId) -> Result<(InstanceConfig, InstanceStatus)> { + Ok(( + self.instance_config(id.clone()).await?, + self.instance_status(id).await?, + )) + } + async fn instances(&self) -> Result>; + async fn export_instances(&self, path: &Path) -> Result<()> { + for id in self.instances().await?.keys() { + self.export_instance(id.clone(), path).await?; + } + Ok(()) + } + async fn copy_from_instance(&self, id: InstanceId, src: &Path, dst: &Path) -> Result<()>; + async fn copy_to_instance(&self, id: InstanceId, src: &Path, dst: &Path) -> Result<()>; + // TODO: Maybe move function to enum InstanceStatus + async fn is_instance_runnable(&self, id: InstanceId) -> Result { + Ok(self.instance_status(id).await? == InstanceStatus::Created) + } + // TODO: Maybe move function to enum InstanceStatus + async fn is_instance_running(&self, id: InstanceId) -> Result { + Ok(self.instance_status(id).await? == InstanceStatus::Running) + } +} + +pub(crate) type InstanceId = String; +#[derive(Default, Clone)] +pub struct InstanceConfig { + // TBD +} + +#[derive(Debug, PartialEq)] +pub enum InstanceStatus { + // TBD + NotCreated, + Requested, + ResourcesReady, + Created, + Stopped, + Running, + Orphaned, + Unknown, +} + +pub struct Instance { + id: InstanceId, + pub config: InstanceConfig, + deployment: Arc, + desired: InstanceStatus, +} + +impl Instance { + pub(super) fn new( + id: InstanceId, + config: InstanceConfig, + deployment: Arc, + desired: InstanceStatus, + ) -> Self { + Self { + id, + config, + deployment, + desired, + } + } + + pub async fn start(&mut self) -> Result<()> { + self.desired = InstanceStatus::Running; + match self.deployment.instance_status(self.id.clone()).await? { + InstanceStatus::Running => Ok(()), + _ => self.deployment.start_instance(self.id.clone()).await, + } + } + + pub async fn stop(&mut self) -> Result<()> { + self.desired = InstanceStatus::Stopped; + match self.deployment.instance_status(self.id.clone()).await? { + InstanceStatus::Stopped => Ok(()), + _ => self.deployment.stop_instance(self.id.clone()).await, + } + } + + pub async fn delete(self) -> Result<(), (anyhow::Error, Self)> { + self.deployment + .delete_instance(self.id.clone()) + .await + .map_err(|e| (e, self)) + } + + pub async fn ready(&mut self) -> Result<()> { + // TODO: Check status, error handling + self.deployment.ready_instance(self.id.clone()).await + } + + pub async fn export(&self, path: &Path) -> Result<()> { + self.deployment.export_instance(self.id.clone(), path).await + } + + pub async fn status(&self) -> Result { + self.deployment.instance_status(self.id.clone()).await + } + + pub async fn copy_from(&self, src: &Path, dst: &Path) -> Result<()> { + self.deployment + .copy_from_instance(self.id.clone(), src, dst) + .await + } + + pub async fn copy_to(&self, src: &Path, dst: &Path) -> Result<()> { + self.deployment + .copy_to_instance(self.id.clone(), src, dst) + .await + } + + pub async fn is_runnable(&self) -> Result { + self.deployment.is_instance_runnable(self.id.clone()).await + } + + pub async fn is_running(&self) -> Result { + self.deployment.is_instance_running(self.id.clone()).await + } +} diff --git a/flecs-core/src/jeweler/mod.rs b/flecs-core/src/jeweler/mod.rs new file mode 100644 index 000000000..c43c13b98 --- /dev/null +++ b/flecs-core/src/jeweler/mod.rs @@ -0,0 +1,6 @@ +pub mod app; +pub mod deployment; +pub mod instance; +pub mod network; +pub mod volume; +pub use super::Result; diff --git a/flecs-core/src/jeweler/network.rs b/flecs-core/src/jeweler/network.rs new file mode 100644 index 000000000..9122b4b53 --- /dev/null +++ b/flecs-core/src/jeweler/network.rs @@ -0,0 +1,23 @@ +use super::Result; +use async_trait::async_trait; +use std::collections::HashMap; +use std::net::IpAddr; + +pub type NetworkId = String; +pub struct Network { + // TBD +} +#[derive(Default)] +pub struct NetworkConfig { + // TBD +} + +#[async_trait] +pub trait NetworkDeployment { + async fn create_network(&self, config: NetworkConfig) -> Result; + async fn delete_network(&self, id: NetworkId) -> Result<()>; + async fn network(&self, id: NetworkId) -> Result; + async fn networks(&self) -> Result>; + async fn connect_network(&self, id: NetworkId, address: IpAddr) -> Result<()>; + async fn disconnect_network(&self, id: NetworkId, address: IpAddr) -> Result<()>; +} diff --git a/flecs-core/src/jeweler/volume.rs b/flecs-core/src/jeweler/volume.rs new file mode 100644 index 000000000..bce6fa573 --- /dev/null +++ b/flecs-core/src/jeweler/volume.rs @@ -0,0 +1,27 @@ +use super::instance::InstanceId; +use super::Result; +use async_trait::async_trait; +use std::collections::HashMap; +use std::path::Path; + +type VolumeId = String; +#[derive(Default)] +pub struct VolumeConfig { + // TBD +} + +#[async_trait] +pub trait VolumeDeployment { + async fn create_volume(&self, config: VolumeConfig) -> Result; + async fn delete_volume(&self, id: VolumeId) -> Result<()>; + async fn import_volume(&self, path: &Path) -> Result; + async fn export_volume(&self, id: VolumeId, path: &Path) -> Result<()>; // TODO: Arguments, return type + async fn volumes(&self, instance_id: InstanceId) -> Result>; + async fn export_volumes(&self, instance_id: InstanceId, path: &Path) -> Result<()> { + // TODO: more logic + for volume_id in self.volumes(instance_id).await?.keys() { + self.export_volume(volume_id.clone(), path).await?; + } + Ok(()) + } +} diff --git a/flecs-core/src/lib.rs b/flecs-core/src/lib.rs index 9a704eed1..aba3c9293 100644 --- a/flecs-core/src/lib.rs +++ b/flecs-core/src/lib.rs @@ -1,6 +1,7 @@ mod cellar; mod flecs_rest; pub mod fsm; +pub mod jeweler; pub mod lore; pub mod quest; pub mod relic; @@ -9,3 +10,4 @@ pub mod vault; pub use anyhow::Error; pub use anyhow::Result; +// TODO: Unify structs (App, Instance, Deployment, ...) with structs from Pouches and move them there