mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 04:01:58 +00:00
Feature/dependency autoconfig (#2588)
* dependency autoconfig * FE portion --------- Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -209,33 +209,24 @@ impl RpcContext {
|
||||
self.services.init(&self).await?;
|
||||
tracing::info!("Initialized Package Managers");
|
||||
|
||||
let mut all_dependency_config_errs = BTreeMap::new();
|
||||
let mut updated_current_dependents = BTreeMap::new();
|
||||
let peek = self.db.peek().await;
|
||||
for (package_id, package) in peek.as_public().as_package_data().as_entries()?.into_iter() {
|
||||
let package = package.clone();
|
||||
let current_dependencies = package.as_current_dependencies().de()?;
|
||||
all_dependency_config_errs.insert(
|
||||
package_id.clone(),
|
||||
compute_dependency_config_errs(
|
||||
self,
|
||||
&peek,
|
||||
&package_id,
|
||||
¤t_dependencies,
|
||||
&Default::default(),
|
||||
)
|
||||
.await?,
|
||||
);
|
||||
let mut current_dependencies = package.as_current_dependencies().de()?;
|
||||
compute_dependency_config_errs(self, &package_id, &mut current_dependencies).await?;
|
||||
updated_current_dependents.insert(package_id.clone(), current_dependencies);
|
||||
}
|
||||
self.db
|
||||
.mutate(|v| {
|
||||
for (package_id, errs) in all_dependency_config_errs {
|
||||
if let Some(config_errors) = v
|
||||
for (package_id, deps) in updated_current_dependents {
|
||||
if let Some(model) = v
|
||||
.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.map(|i| i.as_status_mut().as_dependency_config_errors_mut())
|
||||
.map(|i| i.as_current_dependencies_mut())
|
||||
{
|
||||
config_errors.ser(&errs)?;
|
||||
model.ser(&deps)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -380,6 +380,7 @@ pub struct CurrentDependencyInfo {
|
||||
pub registry_url: Url,
|
||||
#[ts(type = "string")]
|
||||
pub version_spec: VersionRange,
|
||||
pub config_satisfied: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
|
||||
@@ -12,7 +12,6 @@ use crate::config::{Config, ConfigSpec, ConfigureContext};
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::package::CurrentDependencies;
|
||||
use crate::prelude::*;
|
||||
use crate::status::DependencyConfigErrors;
|
||||
use crate::Error;
|
||||
|
||||
pub fn dependency() -> ParentHandler {
|
||||
@@ -180,17 +179,23 @@ pub async fn configure_logic(
|
||||
#[instrument(skip_all)]
|
||||
pub async fn compute_dependency_config_errs(
|
||||
ctx: &RpcContext,
|
||||
db: &Peeked,
|
||||
id: &PackageId,
|
||||
current_dependencies: &CurrentDependencies,
|
||||
dependency_config: &BTreeMap<PackageId, Config>,
|
||||
) -> Result<DependencyConfigErrors, Error> {
|
||||
let mut dependency_config_errs = BTreeMap::new();
|
||||
for (dependency, _dep_info) in current_dependencies.0.iter() {
|
||||
current_dependencies: &mut CurrentDependencies,
|
||||
) -> Result<(), Error> {
|
||||
let service_guard = ctx.services.get(id).await;
|
||||
let service = service_guard.as_ref().or_not_found(id)?;
|
||||
for (dep_id, dep_info) in current_dependencies.0.iter_mut() {
|
||||
// check if config passes dependency check
|
||||
if let Some(error) = todo!() {
|
||||
dependency_config_errs.insert(dependency.clone(), error);
|
||||
}
|
||||
let Some(dependency) = &*ctx.services.get(dep_id).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let dep_config = dependency.get_config().await?.config;
|
||||
|
||||
dep_info.config_satisfied = service
|
||||
.dependency_config(dep_id.clone(), dep_config)
|
||||
.await?
|
||||
.is_none();
|
||||
}
|
||||
Ok(DependencyConfigErrors(dependency_config_errs))
|
||||
Ok(())
|
||||
}
|
||||
|
||||
37
core/startos/src/service/action.rs
Normal file
37
core/startos/src/service/action.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use models::{ActionId, ProcedureName};
|
||||
|
||||
use crate::action::ActionResult;
|
||||
use crate::prelude::*;
|
||||
use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::{BackgroundJobs, Handler};
|
||||
|
||||
struct Action {
|
||||
id: ActionId,
|
||||
input: Value,
|
||||
}
|
||||
impl Handler<Action> for ServiceActor {
|
||||
type Response = Result<ActionResult, Error>;
|
||||
async fn handle(
|
||||
&mut self,
|
||||
Action { id, input }: Action,
|
||||
_: &mut BackgroundJobs,
|
||||
) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
container
|
||||
.execute::<ActionResult>(
|
||||
ProcedureName::RunAction(id),
|
||||
input,
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Action)
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub async fn action(&self, id: ActionId, input: Value) -> Result<ActionResult, Error> {
|
||||
self.actor.send(Action { id, input }).await?
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,52 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use models::ProcedureName;
|
||||
|
||||
use crate::config::action::ConfigRes;
|
||||
use crate::config::ConfigureContext;
|
||||
use crate::prelude::*;
|
||||
use crate::service::Service;
|
||||
use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::{BackgroundJobs, Handler};
|
||||
use crate::util::serde::NoOutput;
|
||||
|
||||
impl Service {
|
||||
pub async fn configure(
|
||||
&self,
|
||||
ConfigureContext { timeout, config }: ConfigureContext,
|
||||
) -> Result<(), Error> {
|
||||
let container = &self.seed.persistent_container;
|
||||
struct Configure(ConfigureContext);
|
||||
impl Handler<Configure> for ServiceActor {
|
||||
type Response = Result<(), Error>;
|
||||
async fn handle(
|
||||
&mut self,
|
||||
Configure(ConfigureContext { timeout, config }): Configure,
|
||||
_: &mut BackgroundJobs,
|
||||
) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
container
|
||||
.execute::<Value>(ProcedureName::SetConfig, to_value(&config)?, timeout)
|
||||
.execute::<NoOutput>(ProcedureName::SetConfig, to_value(&config)?, timeout)
|
||||
.await
|
||||
.with_kind(ErrorKind::Action)?;
|
||||
.with_kind(ErrorKind::ConfigRulesViolation)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct GetConfig;
|
||||
impl Handler<GetConfig> for ServiceActor {
|
||||
type Response = Result<ConfigRes, Error>;
|
||||
async fn handle(&mut self, _: GetConfig, _: &mut BackgroundJobs) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
container
|
||||
.execute::<ConfigRes>(
|
||||
ProcedureName::GetConfig,
|
||||
Value::Null,
|
||||
Some(Duration::from_secs(30)), // TODO timeout
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::ConfigGen)
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub async fn configure(&self, ctx: ConfigureContext) -> Result<(), Error> {
|
||||
self.actor.send(Configure(ctx)).await?
|
||||
}
|
||||
pub async fn get_config(&self) -> Result<ConfigRes, Error> {
|
||||
self.actor.send(GetConfig).await?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::{BackgroundJobs, Handler};
|
||||
|
||||
struct Start;
|
||||
#[async_trait::async_trait]
|
||||
impl Handler<Start> for ServiceActor {
|
||||
type Response = ();
|
||||
async fn handle(&mut self, _: Start, _: &mut BackgroundJobs) -> Self::Response {
|
||||
@@ -22,7 +21,6 @@ impl Service {
|
||||
}
|
||||
|
||||
struct Stop;
|
||||
#[async_trait::async_trait]
|
||||
impl Handler<Stop> for ServiceActor {
|
||||
type Response = ();
|
||||
async fn handle(&mut self, _: Stop, _: &mut BackgroundJobs) -> Self::Response {
|
||||
|
||||
61
core/startos/src/service/dependencies.rs
Normal file
61
core/startos/src/service/dependencies.rs
Normal file
@@ -0,0 +1,61 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use imbl_value::json;
|
||||
use models::{PackageId, ProcedureName};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::{BackgroundJobs, Handler};
|
||||
use crate::Config;
|
||||
|
||||
struct DependencyConfig {
|
||||
dependency_id: PackageId,
|
||||
remote_config: Option<Config>,
|
||||
}
|
||||
impl Handler<DependencyConfig> for ServiceActor {
|
||||
type Response = Result<Option<Config>, Error>;
|
||||
async fn handle(
|
||||
&mut self,
|
||||
DependencyConfig {
|
||||
dependency_id,
|
||||
remote_config,
|
||||
}: DependencyConfig,
|
||||
_: &mut BackgroundJobs,
|
||||
) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
container
|
||||
.sanboxed::<Option<Config>>(
|
||||
ProcedureName::UpdateDependency(dependency_id.clone()),
|
||||
json!({
|
||||
"queryResults": container
|
||||
.execute::<Value>(
|
||||
ProcedureName::QueryDependency(dependency_id),
|
||||
Value::Null,
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Dependency)?,
|
||||
"remoteConfig": remote_config,
|
||||
}),
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Dependency)
|
||||
.map(|res| res.filter(|c| !c.is_empty()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub async fn dependency_config(
|
||||
&self,
|
||||
dependency_id: PackageId,
|
||||
remote_config: Option<Config>,
|
||||
) -> Result<Option<Config>, Error> {
|
||||
self.actor
|
||||
.send(DependencyConfig {
|
||||
dependency_id,
|
||||
remote_config,
|
||||
})
|
||||
.await?
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@ use chrono::{DateTime, Utc};
|
||||
use clap::Parser;
|
||||
use futures::future::BoxFuture;
|
||||
use imbl::OrdMap;
|
||||
use models::{ActionId, HealthCheckId, PackageId, ProcedureName};
|
||||
use models::{HealthCheckId, PackageId, ProcedureName};
|
||||
use persistent_container::PersistentContainer;
|
||||
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, Handler, HandlerArgs};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -13,7 +13,6 @@ use start_stop::StartStop;
|
||||
use tokio::sync::Notify;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::action::ActionResult;
|
||||
use crate::config::action::ConfigRes;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::core::rpc_continuations::RequestGuid;
|
||||
@@ -33,10 +32,13 @@ use crate::util::actor::{Actor, BackgroundJobs, SimpleActor};
|
||||
use crate::util::serde::Pem;
|
||||
use crate::volume::data_dir;
|
||||
|
||||
mod action;
|
||||
pub mod cli;
|
||||
mod config;
|
||||
mod control;
|
||||
mod dependencies;
|
||||
pub mod persistent_container;
|
||||
mod properties;
|
||||
mod rpc;
|
||||
pub mod service_effect_handler;
|
||||
pub mod service_map;
|
||||
@@ -302,43 +304,6 @@ impl Service {
|
||||
Err(Error::new(eyre!("not yet implemented"), ErrorKind::Unknown))
|
||||
}
|
||||
|
||||
pub async fn get_config(&self) -> Result<ConfigRes, Error> {
|
||||
let container = &self.seed.persistent_container;
|
||||
container
|
||||
.execute::<ConfigRes>(
|
||||
ProcedureName::GetConfig,
|
||||
Value::Null,
|
||||
Some(Duration::from_secs(30)), // TODO timeout
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::ConfigGen)
|
||||
}
|
||||
|
||||
// TODO DO the Action Get
|
||||
|
||||
pub async fn action(&self, id: ActionId, input: Value) -> Result<ActionResult, Error> {
|
||||
let container = &self.seed.persistent_container;
|
||||
container
|
||||
.execute::<ActionResult>(
|
||||
ProcedureName::RunAction(id),
|
||||
input,
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Action)
|
||||
}
|
||||
pub async fn properties(&self) -> Result<Value, Error> {
|
||||
let container = &self.seed.persistent_container;
|
||||
container
|
||||
.execute::<Value>(
|
||||
ProcedureName::Properties,
|
||||
Value::Null,
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Unknown)
|
||||
}
|
||||
|
||||
pub async fn shutdown(self) -> Result<(), Error> {
|
||||
self.actor
|
||||
.shutdown(crate::util::actor::PendingMessageStrategy::FinishAll { timeout: None }) // TODO timeout
|
||||
|
||||
21
core/startos/src/service/properties.rs
Normal file
21
core/startos/src/service/properties.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use models::ProcedureName;
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::service::Service;
|
||||
|
||||
impl Service {
|
||||
// TODO: leave here or switch to Actor Message?
|
||||
pub async fn properties(&self) -> Result<Value, Error> {
|
||||
let container = &self.seed.persistent_container;
|
||||
container
|
||||
.execute::<Value>(
|
||||
ProcedureName::Properties,
|
||||
Value::Null,
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Unknown)
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ffi::OsString;
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::unix::process::CommandExt;
|
||||
@@ -1120,51 +1120,64 @@ async fn set_dependencies(
|
||||
) -> Result<(), Error> {
|
||||
let ctx = ctx.deref()?;
|
||||
let id = &ctx.id;
|
||||
let service_guard = ctx.ctx.services.get(id).await;
|
||||
let service = service_guard.as_ref().or_not_found(id)?;
|
||||
let mut deps = BTreeMap::new();
|
||||
for dependency in dependencies {
|
||||
let (dep_id, kind, registry_url, version_spec) = match dependency {
|
||||
DependencyRequirement::Exists {
|
||||
id,
|
||||
registry_url,
|
||||
version_spec,
|
||||
} => (
|
||||
id,
|
||||
CurrentDependencyKind::Exists,
|
||||
registry_url,
|
||||
version_spec,
|
||||
),
|
||||
DependencyRequirement::Running {
|
||||
id,
|
||||
health_checks,
|
||||
registry_url,
|
||||
version_spec,
|
||||
} => (
|
||||
id,
|
||||
CurrentDependencyKind::Running { health_checks },
|
||||
registry_url,
|
||||
version_spec,
|
||||
),
|
||||
};
|
||||
let icon = todo!();
|
||||
let title = todo!();
|
||||
let config_satisfied = if let Some(dep_service) = &*ctx.ctx.services.get(&dep_id).await {
|
||||
service
|
||||
.dependency_config(dep_id, dep_service.get_config().await?.config)
|
||||
.await?
|
||||
.is_none()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
deps.insert(
|
||||
dep_id,
|
||||
CurrentDependencyInfo {
|
||||
kind: CurrentDependencyKind::Exists,
|
||||
registry_url,
|
||||
version_spec,
|
||||
icon,
|
||||
title,
|
||||
config_satisfied,
|
||||
},
|
||||
);
|
||||
}
|
||||
ctx.ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
let dependencies = CurrentDependencies(
|
||||
dependencies
|
||||
.into_iter()
|
||||
.map(|dependency| match dependency {
|
||||
DependencyRequirement::Exists {
|
||||
id,
|
||||
registry_url,
|
||||
version_spec,
|
||||
} => (
|
||||
id,
|
||||
CurrentDependencyInfo {
|
||||
kind: CurrentDependencyKind::Exists,
|
||||
registry_url,
|
||||
version_spec,
|
||||
icon: todo!(),
|
||||
title: todo!(),
|
||||
},
|
||||
),
|
||||
DependencyRequirement::Running {
|
||||
id,
|
||||
health_checks,
|
||||
registry_url,
|
||||
version_spec,
|
||||
} => (
|
||||
id,
|
||||
CurrentDependencyInfo {
|
||||
kind: CurrentDependencyKind::Running { health_checks },
|
||||
registry_url,
|
||||
version_spec,
|
||||
icon: todo!(),
|
||||
title: todo!(),
|
||||
},
|
||||
),
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(id)
|
||||
.or_not_found(id)?
|
||||
.as_current_dependencies_mut()
|
||||
.ser(&dependencies)
|
||||
.ser(&CurrentDependencies(deps))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -165,7 +165,6 @@ impl ServiceMap {
|
||||
status: Status {
|
||||
configured: false,
|
||||
main: MainStatus::Stopped,
|
||||
dependency_config_errors: Default::default(),
|
||||
},
|
||||
marketplace_url: None,
|
||||
developer_key: Pem::new(developer_key),
|
||||
|
||||
@@ -2,16 +2,14 @@ use std::sync::Arc;
|
||||
|
||||
use futures::FutureExt;
|
||||
|
||||
use super::TempDesiredState;
|
||||
use crate::prelude::*;
|
||||
use crate::service::transition::{TransitionKind, TransitionState};
|
||||
use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::{BackgroundJobs, Handler};
|
||||
use crate::util::future::RemoteCancellable;
|
||||
|
||||
use super::TempDesiredState;
|
||||
|
||||
struct Restart;
|
||||
#[async_trait::async_trait]
|
||||
impl Handler<Restart> for ServiceActor {
|
||||
type Response = ();
|
||||
async fn handle(&mut self, _: Restart, jobs: &mut BackgroundJobs) -> Self::Response {
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::collections::BTreeMap;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use imbl::OrdMap;
|
||||
use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
@@ -18,23 +17,6 @@ pub mod health_check;
|
||||
pub struct Status {
|
||||
pub configured: bool,
|
||||
pub main: MainStatus,
|
||||
#[serde(default)]
|
||||
pub dependency_config_errors: DependencyConfigErrors,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, HasModel, Default, TS)]
|
||||
#[model = "Model<Self>"]
|
||||
#[ts(export)]
|
||||
pub struct DependencyConfigErrors(pub BTreeMap<PackageId, String>);
|
||||
impl Map for DependencyConfigErrors {
|
||||
type Key = PackageId;
|
||||
type Value = String;
|
||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||
Ok(key)
|
||||
}
|
||||
fn key_string(key: &Self::Key) -> Result<imbl_value::InternedString, Error> {
|
||||
Ok(key.clone().into())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, TS)]
|
||||
|
||||
@@ -16,10 +16,13 @@ pub trait Actor: Send + 'static {
|
||||
fn init(&mut self, jobs: &mut BackgroundJobs) {}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
pub trait Handler<M>: Actor {
|
||||
type Response: Any + Send;
|
||||
async fn handle(&mut self, msg: M, jobs: &mut BackgroundJobs) -> Self::Response;
|
||||
fn handle(
|
||||
&mut self,
|
||||
msg: M,
|
||||
jobs: &mut BackgroundJobs,
|
||||
) -> impl Future<Output = Self::Response> + Send;
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
|
||||
Reference in New Issue
Block a user