Feature/dependency autoconfig (#2588)

* dependency autoconfig

* FE portion

---------

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Matt Hill
2024-04-03 11:48:26 -06:00
committed by GitHub
parent cc1f14e5e9
commit 3b9298ed2b
23 changed files with 262 additions and 170 deletions

View File

@@ -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,
&current_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(())

View File

@@ -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)]

View File

@@ -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(())
}

View 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?
}
}

View File

@@ -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?
}
}

View File

@@ -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 {

View 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?
}
}

View File

@@ -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

View 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)
}
}

View File

@@ -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
}

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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)]

View File

@@ -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]