mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Refactor/actions (#2733)
* store, properties, manifest * interfaces * init and backups * fix init and backups * file models * more versions * dependencies * config except dynamic types * clean up config * remove disabled from non-dynamic vaues * actions * standardize example code block formats * wip: actions refactor Co-authored-by: Jade <Blu-J@users.noreply.github.com> * commit types * fix types * update types * update action request type * update apis * add description to actionrequest * clean up imports * revert package json * chore: Remove the recursive to the index * chore: Remove the other thing I was testing * flatten action requests * update container runtime with new config paradigm * new actions strategy * seems to be working * misc backend fixes * fix fe bugs * only show breakages if breakages * only show success modal if result * don't panic on failed removal * hide config from actions page * polyfill autoconfig * use metadata strategy for actions instead of prev * misc fixes * chore: split the sdk into 2 libs (#2736) * follow sideload progress (#2718) * follow sideload progress * small bugfix * shareReplay with no refcount false * don't wrap sideload progress in RPCResult * dont present toast --------- Co-authored-by: Aiden McClelland <me@drbonez.dev> * chore: Add the initial of the creation of the two sdk * chore: Add in the baseDist * chore: Add in the baseDist * chore: Get the web and the runtime-container running * chore: Remove the empty file * chore: Fix it so the container-runtime works --------- Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com> Co-authored-by: Aiden McClelland <me@drbonez.dev> * misc fixes * update todos * minor clean up * fix link script * update node version in CI test * fix node version syntax in ci build * wip: fixing callbacks * fix sdk makefile dependencies * add support for const outside of main * update apis * don't panic! * Chore: Capture weird case on rpc, and log that * fix procedure id issue * pass input value for dep auto config * handle disabled and warning for actions * chore: Fix for link not having node_modules * sdk fixes * fix build * fix build * fix build --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> Co-authored-by: Jade <Blu-J@users.noreply.github.com> Co-authored-by: J H <dragondef@gmail.com> Co-authored-by: Jade <2364004+Blu-J@users.noreply.github.com> Co-authored-by: Matt Hill <MattDHill@users.noreply.github.com>
This commit is contained in:
@@ -1,42 +1,38 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::time::Duration;
|
||||
|
||||
use models::{ActionId, ProcedureName};
|
||||
use imbl_value::json;
|
||||
use models::{ActionId, PackageId, ProcedureName, ReplayId};
|
||||
|
||||
use crate::action::ActionResult;
|
||||
use crate::action::{ActionInput, ActionResult};
|
||||
use crate::db::model::package::{ActionRequestCondition, ActionRequestEntry, ActionRequestInput};
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::config::GetConfig;
|
||||
use crate::service::dependencies::DependencyConfig;
|
||||
use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
use crate::util::actor::{ConflictBuilder, Handler};
|
||||
use crate::util::serde::is_partial_of;
|
||||
|
||||
pub(super) struct Action {
|
||||
pub(super) struct GetActionInput {
|
||||
id: ActionId,
|
||||
input: Value,
|
||||
}
|
||||
impl Handler<Action> for ServiceActor {
|
||||
type Response = Result<ActionResult, Error>;
|
||||
fn conflicts_with(_: &Action) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::everything()
|
||||
.except::<GetConfig>()
|
||||
.except::<DependencyConfig>()
|
||||
impl Handler<GetActionInput> for ServiceActor {
|
||||
type Response = Result<Option<ActionInput>, Error>;
|
||||
fn conflicts_with(_: &GetActionInput) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::nothing()
|
||||
}
|
||||
async fn handle(
|
||||
&mut self,
|
||||
id: Guid,
|
||||
Action {
|
||||
id: action_id,
|
||||
input,
|
||||
}: Action,
|
||||
GetActionInput { id: action_id }: GetActionInput,
|
||||
_: &BackgroundJobQueue,
|
||||
) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
container
|
||||
.execute::<ActionResult>(
|
||||
.execute::<Option<ActionInput>>(
|
||||
id,
|
||||
ProcedureName::RunAction(action_id),
|
||||
input,
|
||||
ProcedureName::GetActionInput(action_id),
|
||||
Value::Null,
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
@@ -45,16 +41,139 @@ impl Handler<Action> for ServiceActor {
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub async fn action(
|
||||
pub async fn get_action_input(
|
||||
&self,
|
||||
id: Guid,
|
||||
action_id: ActionId,
|
||||
) -> Result<Option<ActionInput>, Error> {
|
||||
if !self
|
||||
.seed
|
||||
.ctx
|
||||
.db
|
||||
.peek()
|
||||
.await
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_idx(&self.seed.id)
|
||||
.or_not_found(&self.seed.id)?
|
||||
.as_actions()
|
||||
.as_idx(&action_id)
|
||||
.or_not_found(&action_id)?
|
||||
.as_has_input()
|
||||
.de()?
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
self.actor
|
||||
.send(id, GetActionInput { id: action_id })
|
||||
.await?
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update_requested_actions(
|
||||
requested_actions: &mut BTreeMap<ReplayId, ActionRequestEntry>,
|
||||
package_id: &PackageId,
|
||||
action_id: &ActionId,
|
||||
input: &Value,
|
||||
was_run: bool,
|
||||
) {
|
||||
requested_actions.retain(|_, v| {
|
||||
if &v.request.package_id != package_id || &v.request.action_id != action_id {
|
||||
return true;
|
||||
}
|
||||
if let Some(when) = &v.request.when {
|
||||
match &when.condition {
|
||||
ActionRequestCondition::InputNotMatches => match &v.request.input {
|
||||
Some(ActionRequestInput::Partial { value }) => {
|
||||
if is_partial_of(value, input) {
|
||||
if when.once {
|
||||
return !was_run;
|
||||
} else {
|
||||
v.active = false;
|
||||
}
|
||||
} else {
|
||||
v.active = true;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
tracing::error!(
|
||||
"action request exists in an invalid state {:?}",
|
||||
v.request
|
||||
);
|
||||
}
|
||||
},
|
||||
}
|
||||
true
|
||||
} else {
|
||||
!was_run
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) struct RunAction {
|
||||
id: ActionId,
|
||||
input: Value,
|
||||
}
|
||||
impl Handler<RunAction> for ServiceActor {
|
||||
type Response = Result<Option<ActionResult>, Error>;
|
||||
fn conflicts_with(_: &RunAction) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::everything().except::<GetActionInput>()
|
||||
}
|
||||
async fn handle(
|
||||
&mut self,
|
||||
id: Guid,
|
||||
RunAction {
|
||||
id: action_id,
|
||||
input,
|
||||
}: RunAction,
|
||||
_: &BackgroundJobQueue,
|
||||
) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
let result = container
|
||||
.execute::<Option<ActionResult>>(
|
||||
id,
|
||||
ProcedureName::RunAction(action_id.clone()),
|
||||
json!({
|
||||
"input": input,
|
||||
}),
|
||||
Some(Duration::from_secs(30)),
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::Action)?;
|
||||
let package_id = &self.0.id;
|
||||
self.0
|
||||
.ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
||||
pde.as_requested_actions_mut().mutate(|requested_actions| {
|
||||
Ok(update_requested_actions(
|
||||
requested_actions,
|
||||
package_id,
|
||||
&action_id,
|
||||
&input,
|
||||
true,
|
||||
))
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub async fn run_action(
|
||||
&self,
|
||||
id: Guid,
|
||||
action_id: ActionId,
|
||||
input: Value,
|
||||
) -> Result<ActionResult, Error> {
|
||||
) -> Result<Option<ActionResult>, Error> {
|
||||
self.actor
|
||||
.send(
|
||||
id,
|
||||
Action {
|
||||
RunAction {
|
||||
id: action_id,
|
||||
input,
|
||||
},
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use models::ProcedureName;
|
||||
|
||||
use crate::config::action::ConfigRes;
|
||||
use crate::config::ConfigureContext;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::dependencies::DependencyConfig;
|
||||
use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
use crate::util::actor::{ConflictBuilder, Handler};
|
||||
use crate::util::serde::NoOutput;
|
||||
|
||||
pub(super) struct Configure(ConfigureContext);
|
||||
impl Handler<Configure> for ServiceActor {
|
||||
type Response = Result<(), Error>;
|
||||
fn conflicts_with(_: &Configure) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::everything().except::<DependencyConfig>()
|
||||
}
|
||||
async fn handle(
|
||||
&mut self,
|
||||
id: Guid,
|
||||
Configure(ConfigureContext { timeout, config }): Configure,
|
||||
_: &BackgroundJobQueue,
|
||||
) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
let package_id = &self.0.id;
|
||||
|
||||
container
|
||||
.execute::<NoOutput>(id, ProcedureName::SetConfig, to_value(&config)?, timeout)
|
||||
.await
|
||||
.with_kind(ErrorKind::ConfigRulesViolation)?;
|
||||
self.0
|
||||
.ctx
|
||||
.db
|
||||
.mutate(move |db| {
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(package_id)
|
||||
.or_not_found(package_id)?
|
||||
.as_status_mut()
|
||||
.as_configured_mut()
|
||||
.ser(&true)
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct GetConfig;
|
||||
impl Handler<GetConfig> for ServiceActor {
|
||||
type Response = Result<ConfigRes, Error>;
|
||||
fn conflicts_with(_: &GetConfig) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::nothing().except::<Configure>()
|
||||
}
|
||||
async fn handle(&mut self, id: Guid, _: GetConfig, _: &BackgroundJobQueue) -> Self::Response {
|
||||
let container = &self.0.persistent_container;
|
||||
container
|
||||
.execute::<ConfigRes>(
|
||||
id,
|
||||
ProcedureName::GetConfig,
|
||||
Value::Null,
|
||||
Some(Duration::from_secs(30)), // TODO timeout
|
||||
)
|
||||
.await
|
||||
.with_kind(ErrorKind::ConfigRulesViolation)
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub async fn configure(&self, id: Guid, ctx: ConfigureContext) -> Result<(), Error> {
|
||||
self.actor.send(id, Configure(ctx)).await?
|
||||
}
|
||||
pub async fn get_config(&self, id: Guid) -> Result<ConfigRes, Error> {
|
||||
self.actor.send(id, GetConfig).await?
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::config::GetConfig;
|
||||
use crate::service::dependencies::DependencyConfig;
|
||||
use crate::service::action::RunAction;
|
||||
use crate::service::start_stop::StartStop;
|
||||
use crate::service::transition::TransitionKind;
|
||||
use crate::service::{Service, ServiceActor};
|
||||
@@ -12,9 +11,7 @@ pub(super) struct Start;
|
||||
impl Handler<Start> for ServiceActor {
|
||||
type Response = ();
|
||||
fn conflicts_with(_: &Start) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::everything()
|
||||
.except::<GetConfig>()
|
||||
.except::<DependencyConfig>()
|
||||
ConflictBuilder::everything().except::<RunAction>()
|
||||
}
|
||||
async fn handle(&mut self, _: Guid, _: Start, _: &BackgroundJobQueue) -> Self::Response {
|
||||
self.0.persistent_container.state.send_modify(|x| {
|
||||
@@ -33,9 +30,7 @@ struct Stop;
|
||||
impl Handler<Stop> for ServiceActor {
|
||||
type Response = ();
|
||||
fn conflicts_with(_: &Stop) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::everything()
|
||||
.except::<GetConfig>()
|
||||
.except::<DependencyConfig>()
|
||||
ConflictBuilder::everything().except::<RunAction>()
|
||||
}
|
||||
async fn handle(&mut self, _: Guid, _: Stop, _: &BackgroundJobQueue) -> Self::Response {
|
||||
let mut transition_state = None;
|
||||
|
||||
@@ -1,86 +0,0 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use imbl_value::json;
|
||||
use models::{PackageId, ProcedureName};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::{Service, ServiceActor, ServiceActorSeed};
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
use crate::util::actor::{ConflictBuilder, Handler};
|
||||
use crate::Config;
|
||||
|
||||
impl ServiceActorSeed {
|
||||
async fn dependency_config(
|
||||
&self,
|
||||
id: Guid,
|
||||
dependency_id: PackageId,
|
||||
remote_config: Option<Config>,
|
||||
) -> Result<Option<Config>, Error> {
|
||||
let container = &self.persistent_container;
|
||||
container
|
||||
.sanboxed::<Option<Config>>(
|
||||
id.clone(),
|
||||
ProcedureName::UpdateDependency(dependency_id.clone()),
|
||||
json!({
|
||||
"queryResults": container
|
||||
.execute::<Value>(
|
||||
id,
|
||||
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() && Some(c) != remote_config.as_ref()))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct DependencyConfig {
|
||||
dependency_id: PackageId,
|
||||
remote_config: Option<Config>,
|
||||
}
|
||||
impl Handler<DependencyConfig> for ServiceActor {
|
||||
type Response = Result<Option<Config>, Error>;
|
||||
fn conflicts_with(_: &DependencyConfig) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::nothing()
|
||||
}
|
||||
async fn handle(
|
||||
&mut self,
|
||||
id: Guid,
|
||||
DependencyConfig {
|
||||
dependency_id,
|
||||
remote_config,
|
||||
}: DependencyConfig,
|
||||
_: &BackgroundJobQueue,
|
||||
) -> Self::Response {
|
||||
self.0
|
||||
.dependency_config(id, dependency_id, remote_config)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
impl Service {
|
||||
pub async fn dependency_config(
|
||||
&self,
|
||||
id: Guid,
|
||||
dependency_id: PackageId,
|
||||
remote_config: Option<Config>,
|
||||
) -> Result<Option<Config>, Error> {
|
||||
self.actor
|
||||
.send(
|
||||
id,
|
||||
DependencyConfig {
|
||||
dependency_id,
|
||||
remote_config,
|
||||
},
|
||||
)
|
||||
.await?
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,59 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use models::{ActionId, PackageId};
|
||||
use models::{ActionId, PackageId, ReplayId};
|
||||
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
|
||||
|
||||
use crate::action::ActionResult;
|
||||
use crate::db::model::package::ActionMetadata;
|
||||
use crate::action::{display_action_result, ActionInput, ActionResult};
|
||||
use crate::db::model::package::{
|
||||
ActionMetadata, ActionRequest, ActionRequestCondition, ActionRequestEntry, ActionRequestTrigger,
|
||||
};
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::cli::ContainerCliContext;
|
||||
use crate::service::effects::prelude::*;
|
||||
use crate::util::serde::HandlerExtSerde;
|
||||
|
||||
pub fn action_api<C: Context>() -> ParentHandler<C> {
|
||||
ParentHandler::new()
|
||||
.subcommand("export", from_fn_async(export_action).no_cli())
|
||||
.subcommand(
|
||||
"clear",
|
||||
from_fn_async(clear_actions)
|
||||
.no_display()
|
||||
.with_call_remote::<ContainerCliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"get-input",
|
||||
from_fn_async(get_action_input)
|
||||
.with_display_serializable()
|
||||
.with_call_remote::<ContainerCliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"run",
|
||||
from_fn_async(run_action)
|
||||
.with_display_serializable()
|
||||
.with_custom_display_fn(|args, res| Ok(display_action_result(args.params, res)))
|
||||
.with_call_remote::<ContainerCliContext>(),
|
||||
)
|
||||
.subcommand("request", from_fn_async(request_action).no_cli())
|
||||
.subcommand(
|
||||
"clear-requests",
|
||||
from_fn_async(clear_action_requests)
|
||||
.no_display()
|
||||
.with_call_remote::<ContainerCliContext>(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ExportActionParams {
|
||||
#[ts(optional)]
|
||||
package_id: Option<PackageId>,
|
||||
id: ActionId,
|
||||
metadata: ActionMetadata,
|
||||
}
|
||||
pub async fn export_action(context: EffectContext, data: ExportActionParams) -> Result<(), Error> {
|
||||
pub async fn export_action(
|
||||
context: EffectContext,
|
||||
ExportActionParams { id, metadata }: ExportActionParams,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
let package_id = context.seed.id.clone();
|
||||
context
|
||||
@@ -31,17 +68,26 @@ pub async fn export_action(context: EffectContext, data: ExportActionParams) ->
|
||||
.or_not_found(&package_id)?
|
||||
.as_actions_mut();
|
||||
let mut value = model.de()?;
|
||||
value
|
||||
.insert(data.id, data.metadata)
|
||||
.map(|_| ())
|
||||
.unwrap_or_default();
|
||||
value.insert(id, metadata);
|
||||
model.ser(&value)
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn clear_actions(context: EffectContext) -> Result<(), Error> {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClearActionsParams {
|
||||
#[arg(long)]
|
||||
pub except: Vec<ActionId>,
|
||||
}
|
||||
|
||||
async fn clear_actions(
|
||||
context: EffectContext,
|
||||
ClearActionsParams { except }: ClearActionsParams,
|
||||
) -> Result<(), Error> {
|
||||
let except: BTreeSet<_> = except.into_iter().collect();
|
||||
let context = context.deref()?;
|
||||
let package_id = context.seed.id.clone();
|
||||
context
|
||||
@@ -54,34 +100,32 @@ pub async fn clear_actions(context: EffectContext) -> Result<(), Error> {
|
||||
.as_idx_mut(&package_id)
|
||||
.or_not_found(&package_id)?
|
||||
.as_actions_mut()
|
||||
.ser(&BTreeMap::new())
|
||||
.mutate(|a| Ok(a.retain(|e, _| except.contains(e))))
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct ExecuteAction {
|
||||
pub struct GetActionInputParams {
|
||||
#[serde(default)]
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
procedure_id: Guid,
|
||||
#[ts(optional)]
|
||||
package_id: Option<PackageId>,
|
||||
action_id: ActionId,
|
||||
#[ts(type = "any")]
|
||||
input: Value,
|
||||
}
|
||||
pub async fn execute_action(
|
||||
async fn get_action_input(
|
||||
context: EffectContext,
|
||||
ExecuteAction {
|
||||
GetActionInputParams {
|
||||
procedure_id,
|
||||
package_id,
|
||||
action_id,
|
||||
input,
|
||||
}: ExecuteAction,
|
||||
) -> Result<ActionResult, Error> {
|
||||
}: GetActionInputParams,
|
||||
) -> Result<Option<ActionInput>, Error> {
|
||||
let context = context.deref()?;
|
||||
|
||||
if let Some(package_id) = package_id {
|
||||
@@ -93,9 +137,179 @@ pub async fn execute_action(
|
||||
.await
|
||||
.as_ref()
|
||||
.or_not_found(&package_id)?
|
||||
.action(procedure_id, action_id, input)
|
||||
.get_action_input(procedure_id, action_id)
|
||||
.await
|
||||
} else {
|
||||
context.action(procedure_id, action_id, input).await
|
||||
context.get_action_input(procedure_id, action_id).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RunActionParams {
|
||||
#[serde(default)]
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
procedure_id: Guid,
|
||||
#[ts(optional)]
|
||||
package_id: Option<PackageId>,
|
||||
action_id: ActionId,
|
||||
#[ts(type = "any")]
|
||||
input: Value,
|
||||
}
|
||||
async fn run_action(
|
||||
context: EffectContext,
|
||||
RunActionParams {
|
||||
procedure_id,
|
||||
package_id,
|
||||
action_id,
|
||||
input,
|
||||
}: RunActionParams,
|
||||
) -> Result<Option<ActionResult>, Error> {
|
||||
let context = context.deref()?;
|
||||
|
||||
let package_id = package_id.as_ref().unwrap_or(&context.seed.id);
|
||||
|
||||
if package_id != &context.seed.id {
|
||||
return Err(Error::new(
|
||||
eyre!("calling actions on other packages is unsupported at this time"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
context
|
||||
.seed
|
||||
.ctx
|
||||
.services
|
||||
.get(&package_id)
|
||||
.await
|
||||
.as_ref()
|
||||
.or_not_found(&package_id)?
|
||||
.run_action(procedure_id, action_id, input)
|
||||
.await
|
||||
} else {
|
||||
context.run_action(procedure_id, action_id, input).await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct RequestActionParams {
|
||||
#[serde(default)]
|
||||
#[ts(skip)]
|
||||
procedure_id: Guid,
|
||||
replay_id: ReplayId,
|
||||
#[serde(flatten)]
|
||||
request: ActionRequest,
|
||||
}
|
||||
async fn request_action(
|
||||
context: EffectContext,
|
||||
RequestActionParams {
|
||||
procedure_id,
|
||||
replay_id,
|
||||
request,
|
||||
}: RequestActionParams,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
|
||||
let src_id = &context.seed.id;
|
||||
let active = match &request.when {
|
||||
Some(ActionRequestTrigger { once, condition }) => match condition {
|
||||
ActionRequestCondition::InputNotMatches => {
|
||||
let Some(input) = request.input.as_ref() else {
|
||||
return Err(Error::new(
|
||||
eyre!("input-not-matches trigger requires input to be specified"),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
if let Some(service) = context
|
||||
.seed
|
||||
.ctx
|
||||
.services
|
||||
.get(&request.package_id)
|
||||
.await
|
||||
.as_ref()
|
||||
{
|
||||
let Some(prev) = service
|
||||
.get_action_input(procedure_id, request.action_id.clone())
|
||||
.await?
|
||||
else {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"action {} of {} has no input",
|
||||
request.action_id,
|
||||
request.package_id
|
||||
),
|
||||
ErrorKind::InvalidRequest,
|
||||
));
|
||||
};
|
||||
if input.matches(prev.value.as_ref()) {
|
||||
if *once {
|
||||
return Ok(());
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true // update when service is installed
|
||||
}
|
||||
}
|
||||
},
|
||||
None => true,
|
||||
};
|
||||
context
|
||||
.seed
|
||||
.ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(src_id)
|
||||
.or_not_found(src_id)?
|
||||
.as_requested_actions_mut()
|
||||
.insert(&replay_id, &ActionRequestEntry { active, request })
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
|
||||
#[ts(type = "{ only: string[] } | { except: string[] }")]
|
||||
#[ts(export)]
|
||||
pub struct ClearActionRequestsParams {
|
||||
#[arg(long, conflicts_with = "except")]
|
||||
pub only: Option<Vec<ReplayId>>,
|
||||
#[arg(long, conflicts_with = "only")]
|
||||
pub except: Option<Vec<ReplayId>>,
|
||||
}
|
||||
|
||||
async fn clear_action_requests(
|
||||
context: EffectContext,
|
||||
ClearActionRequestsParams { only, except }: ClearActionRequestsParams,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
let package_id = context.seed.id.clone();
|
||||
let only = only.map(|only| only.into_iter().collect::<BTreeSet<_>>());
|
||||
let except = except.map(|except| except.into_iter().collect::<BTreeSet<_>>());
|
||||
context
|
||||
.seed
|
||||
.ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&package_id)
|
||||
.or_not_found(&package_id)?
|
||||
.as_requested_actions_mut()
|
||||
.mutate(|a| {
|
||||
Ok(a.retain(|e, _| {
|
||||
only.as_ref().map_or(true, |only| !only.contains(e))
|
||||
&& except.as_ref().map_or(true, |except| except.contains(e))
|
||||
}))
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -3,19 +3,22 @@ use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::sync::{Arc, Mutex, Weak};
|
||||
use std::time::{Duration, SystemTime};
|
||||
|
||||
use clap::Parser;
|
||||
use futures::future::join_all;
|
||||
use helpers::NonDetachingJoinHandle;
|
||||
use imbl::{vector, Vector};
|
||||
use imbl_value::InternedString;
|
||||
use models::{HostId, PackageId, ServiceInterfaceId};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::net::ssl::FullchainCertData;
|
||||
use crate::prelude::*;
|
||||
use crate::service::effects::context::EffectContext;
|
||||
use crate::service::effects::net::ssl::Algorithm;
|
||||
use crate::service::rpc::CallbackHandle;
|
||||
use crate::service::rpc::{CallbackHandle, CallbackId};
|
||||
use crate::service::{Service, ServiceActorSeed};
|
||||
use crate::util::collections::EqMap;
|
||||
|
||||
@@ -272,6 +275,7 @@ impl CallbackHandler {
|
||||
}
|
||||
}
|
||||
pub async fn call(mut self, args: Vector<Value>) -> Result<(), Error> {
|
||||
dbg!(eyre!("callback fired: {}", self.handle.is_active()));
|
||||
if let Some(seed) = self.seed.upgrade() {
|
||||
seed.persistent_container
|
||||
.callback(self.handle.take(), args)
|
||||
@@ -299,13 +303,29 @@ impl CallbackHandlers {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn clear_callbacks(context: EffectContext) -> Result<(), Error> {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
|
||||
#[ts(type = "{ only: number[] } | { except: number[] }")]
|
||||
#[ts(export)]
|
||||
pub struct ClearCallbacksParams {
|
||||
#[arg(long, conflicts_with = "except")]
|
||||
pub only: Option<Vec<CallbackId>>,
|
||||
#[arg(long, conflicts_with = "only")]
|
||||
pub except: Option<Vec<CallbackId>>,
|
||||
}
|
||||
|
||||
pub(super) fn clear_callbacks(
|
||||
context: EffectContext,
|
||||
ClearCallbacksParams { only, except }: ClearCallbacksParams,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
context
|
||||
.seed
|
||||
.persistent_container
|
||||
.state
|
||||
.send_if_modified(|s| !std::mem::take(&mut s.callbacks).is_empty());
|
||||
let only = only.map(|only| only.into_iter().collect::<BTreeSet<_>>());
|
||||
let except = except.map(|except| except.into_iter().collect::<BTreeSet<_>>());
|
||||
context.seed.persistent_container.state.send_modify(|s| {
|
||||
s.callbacks.retain(|cb| {
|
||||
only.as_ref().map_or(true, |only| !only.contains(cb))
|
||||
&& except.as_ref().map_or(true, |except| except.contains(cb))
|
||||
})
|
||||
});
|
||||
context.seed.ctx.callbacks.gc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
use models::PackageId;
|
||||
|
||||
use crate::service::effects::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct GetConfiguredParams {
|
||||
#[ts(optional)]
|
||||
package_id: Option<PackageId>,
|
||||
}
|
||||
pub async fn get_configured(context: EffectContext) -> Result<bool, Error> {
|
||||
let context = context.deref()?;
|
||||
let peeked = context.seed.ctx.db.peek().await;
|
||||
let package_id = &context.seed.id;
|
||||
peeked
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_idx(package_id)
|
||||
.or_not_found(package_id)?
|
||||
.as_status()
|
||||
.as_configured()
|
||||
.de()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Parser, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct SetConfigured {
|
||||
configured: bool,
|
||||
}
|
||||
pub async fn set_configured(
|
||||
context: EffectContext,
|
||||
SetConfigured { configured }: SetConfigured,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
let package_id = &context.seed.id;
|
||||
context
|
||||
.seed
|
||||
.ctx
|
||||
.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(package_id)
|
||||
.or_not_found(package_id)?
|
||||
.as_status_mut()
|
||||
.as_configured_mut()
|
||||
.ser(&configured)
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use models::FromStrParser;
|
||||
|
||||
use crate::service::effects::prelude::*;
|
||||
use crate::util::clap::FromStrParser;
|
||||
|
||||
pub async fn restart(
|
||||
context: EffectContext,
|
||||
|
||||
@@ -6,13 +6,13 @@ use clap::builder::ValueParserFactory;
|
||||
use exver::VersionRange;
|
||||
use imbl::OrdMap;
|
||||
use imbl_value::InternedString;
|
||||
use itertools::Itertools;
|
||||
use models::{HealthCheckId, PackageId, VersionString, VolumeId};
|
||||
use models::{FromStrParser, HealthCheckId, PackageId, ReplayId, VersionString, VolumeId};
|
||||
use patch_db::json_ptr::JsonPointer;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::db::model::package::{
|
||||
CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind, ManifestPreference,
|
||||
ActionRequestEntry, CurrentDependencies, CurrentDependencyInfo, CurrentDependencyKind,
|
||||
ManifestPreference,
|
||||
};
|
||||
use crate::disk::mount::filesystem::bind::Bind;
|
||||
use crate::disk::mount::filesystem::idmapped::IdMapped;
|
||||
@@ -20,7 +20,6 @@ use crate::disk::mount::filesystem::{FileSystem, MountType};
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::effects::prelude::*;
|
||||
use crate::status::health_check::NamedHealthCheckResult;
|
||||
use crate::util::clap::FromStrParser;
|
||||
use crate::util::Invoke;
|
||||
use crate::volume::data_dir;
|
||||
|
||||
@@ -113,6 +112,7 @@ pub async fn expose_for_dependents(
|
||||
context: EffectContext,
|
||||
ExposeForDependentsParams { paths }: ExposeForDependentsParams,
|
||||
) -> Result<(), Error> {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -192,16 +192,11 @@ impl ValueParserFactory for DependencyRequirement {
|
||||
#[command(rename_all = "camelCase")]
|
||||
#[ts(export)]
|
||||
pub struct SetDependenciesParams {
|
||||
#[serde(default)]
|
||||
procedure_id: Guid,
|
||||
dependencies: Vec<DependencyRequirement>,
|
||||
}
|
||||
pub async fn set_dependencies(
|
||||
context: EffectContext,
|
||||
SetDependenciesParams {
|
||||
procedure_id,
|
||||
dependencies,
|
||||
}: SetDependenciesParams,
|
||||
SetDependenciesParams { dependencies }: SetDependenciesParams,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
let id = &context.seed.id;
|
||||
@@ -222,19 +217,6 @@ pub async fn set_dependencies(
|
||||
version_range,
|
||||
),
|
||||
};
|
||||
let config_satisfied =
|
||||
if let Some(dep_service) = &*context.seed.ctx.services.get(&dep_id).await {
|
||||
context
|
||||
.dependency_config(
|
||||
procedure_id.clone(),
|
||||
dep_id.clone(),
|
||||
dep_service.get_config(procedure_id.clone()).await?.config,
|
||||
)
|
||||
.await?
|
||||
.is_none()
|
||||
} else {
|
||||
true
|
||||
};
|
||||
let info = CurrentDependencyInfo {
|
||||
title: context
|
||||
.seed
|
||||
@@ -251,7 +233,6 @@ pub async fn set_dependencies(
|
||||
.await?,
|
||||
kind,
|
||||
version_range,
|
||||
config_satisfied,
|
||||
};
|
||||
deps.insert(dep_id, info);
|
||||
}
|
||||
@@ -282,7 +263,8 @@ pub async fn get_dependencies(context: EffectContext) -> Result<Vec<DependencyRe
|
||||
.as_current_dependencies()
|
||||
.de()?;
|
||||
|
||||
data.0
|
||||
Ok(data
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(id, current_dependency_info)| {
|
||||
let CurrentDependencyInfo {
|
||||
@@ -290,7 +272,7 @@ pub async fn get_dependencies(context: EffectContext) -> Result<Vec<DependencyRe
|
||||
kind,
|
||||
..
|
||||
} = current_dependency_info;
|
||||
Ok::<_, Error>(match kind {
|
||||
match kind {
|
||||
CurrentDependencyKind::Exists => {
|
||||
DependencyRequirement::Exists { id, version_range }
|
||||
}
|
||||
@@ -301,9 +283,9 @@ pub async fn get_dependencies(context: EffectContext) -> Result<Vec<DependencyRe
|
||||
version_range,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.try_collect()
|
||||
.collect())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, Parser, TS)]
|
||||
@@ -320,12 +302,10 @@ pub struct CheckDependenciesResult {
|
||||
package_id: PackageId,
|
||||
#[ts(type = "string | null")]
|
||||
title: Option<InternedString>,
|
||||
#[ts(type = "string | null")]
|
||||
installed_version: Option<exver::ExtendedVersion>,
|
||||
#[ts(type = "string[]")]
|
||||
installed_version: Option<VersionString>,
|
||||
satisfies: BTreeSet<VersionString>,
|
||||
is_running: bool,
|
||||
config_satisfied: bool,
|
||||
requested_actions: BTreeMap<ReplayId, ActionRequestEntry>,
|
||||
#[ts(as = "BTreeMap::<HealthCheckId, NamedHealthCheckResult>")]
|
||||
health_checks: OrdMap<HealthCheckId, NamedHealthCheckResult>,
|
||||
}
|
||||
@@ -335,14 +315,14 @@ pub async fn check_dependencies(
|
||||
) -> Result<Vec<CheckDependenciesResult>, Error> {
|
||||
let context = context.deref()?;
|
||||
let db = context.seed.ctx.db.peek().await;
|
||||
let current_dependencies = db
|
||||
let pde = db
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_idx(&context.seed.id)
|
||||
.or_not_found(&context.seed.id)?
|
||||
.as_current_dependencies()
|
||||
.de()?;
|
||||
let package_ids: Vec<_> = package_ids
|
||||
.or_not_found(&context.seed.id)?;
|
||||
let current_dependencies = pde.as_current_dependencies().de()?;
|
||||
let requested_actions = pde.as_requested_actions().de()?;
|
||||
let package_dependency_info: Vec<_> = package_ids
|
||||
.unwrap_or_else(|| current_dependencies.0.keys().cloned().collect())
|
||||
.into_iter()
|
||||
.filter_map(|x| {
|
||||
@@ -350,18 +330,23 @@ pub async fn check_dependencies(
|
||||
Some((x, info))
|
||||
})
|
||||
.collect();
|
||||
let mut results = Vec::with_capacity(package_ids.len());
|
||||
let mut results = Vec::with_capacity(package_dependency_info.len());
|
||||
|
||||
for (package_id, dependency_info) in package_ids {
|
||||
for (package_id, dependency_info) in package_dependency_info {
|
||||
let title = dependency_info.title.clone();
|
||||
let Some(package) = db.as_public().as_package_data().as_idx(&package_id) else {
|
||||
let requested_actions = requested_actions
|
||||
.iter()
|
||||
.filter(|(_, v)| v.request.package_id == package_id)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
results.push(CheckDependenciesResult {
|
||||
package_id,
|
||||
title,
|
||||
installed_version: None,
|
||||
satisfies: BTreeSet::new(),
|
||||
is_running: false,
|
||||
config_satisfied: false,
|
||||
requested_actions,
|
||||
health_checks: Default::default(),
|
||||
});
|
||||
continue;
|
||||
@@ -369,22 +354,27 @@ pub async fn check_dependencies(
|
||||
let manifest = package.as_state_info().as_manifest(ManifestPreference::New);
|
||||
let installed_version = manifest.as_version().de()?.into_version();
|
||||
let satisfies = manifest.as_satisfies().de()?;
|
||||
let installed_version = Some(installed_version.clone());
|
||||
let installed_version = Some(installed_version.clone().into());
|
||||
let is_installed = true;
|
||||
let status = package.as_status().as_main().de()?;
|
||||
let status = package.as_status().de()?;
|
||||
let is_running = if is_installed {
|
||||
status.running()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
let health_checks = status.health().cloned().unwrap_or_default();
|
||||
let requested_actions = requested_actions
|
||||
.iter()
|
||||
.filter(|(_, v)| v.request.package_id == package_id)
|
||||
.map(|(k, v)| (k.clone(), v.clone()))
|
||||
.collect();
|
||||
results.push(CheckDependenciesResult {
|
||||
package_id,
|
||||
title,
|
||||
installed_version,
|
||||
satisfies,
|
||||
is_running,
|
||||
config_satisfied: dependency_info.config_satisfied,
|
||||
requested_actions,
|
||||
health_checks,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ pub async fn set_health(
|
||||
.as_idx_mut(package_id)
|
||||
.or_not_found(package_id)?
|
||||
.as_status_mut()
|
||||
.as_main_mut()
|
||||
.mutate(|main| {
|
||||
match main {
|
||||
MainStatus::Running { ref mut health, .. }
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::service::effects::context::EffectContext;
|
||||
|
||||
mod action;
|
||||
pub mod callbacks;
|
||||
mod config;
|
||||
pub mod context;
|
||||
mod control;
|
||||
mod dependency;
|
||||
@@ -26,34 +25,12 @@ pub fn handler<C: Context>() -> ParentHandler<C> {
|
||||
from_fn(echo::<EffectContext>).with_call_remote::<ContainerCliContext>(),
|
||||
)
|
||||
// action
|
||||
.subcommand(
|
||||
"execute-action",
|
||||
from_fn_async(action::execute_action).no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"export-action",
|
||||
from_fn_async(action::export_action).no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"clear-actions",
|
||||
from_fn_async(action::clear_actions).no_cli(),
|
||||
)
|
||||
.subcommand("action", action::action_api::<C>())
|
||||
// callbacks
|
||||
.subcommand(
|
||||
"clear-callbacks",
|
||||
from_fn(callbacks::clear_callbacks).no_cli(),
|
||||
)
|
||||
// config
|
||||
.subcommand(
|
||||
"get-configured",
|
||||
from_fn_async(config::get_configured).no_cli(),
|
||||
)
|
||||
.subcommand(
|
||||
"set-configured",
|
||||
from_fn_async(config::set_configured)
|
||||
.no_display()
|
||||
.with_call_remote::<ContainerCliContext>(),
|
||||
)
|
||||
// control
|
||||
.subcommand(
|
||||
"restart",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use models::{HostId, PackageId};
|
||||
|
||||
use crate::net::host::binding::{BindOptions, LanInfo};
|
||||
use crate::net::host::binding::{BindId, BindOptions, LanInfo};
|
||||
use crate::net::host::HostKind;
|
||||
use crate::service::effects::prelude::*;
|
||||
|
||||
@@ -28,10 +28,20 @@ pub async fn bind(
|
||||
svc.bind(kind, id, internal_port, options).await
|
||||
}
|
||||
|
||||
pub async fn clear_bindings(context: EffectContext) -> Result<(), Error> {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClearBindingsParams {
|
||||
pub except: Vec<BindId>,
|
||||
}
|
||||
|
||||
pub async fn clear_bindings(
|
||||
context: EffectContext,
|
||||
ClearBindingsParams { except }: ClearBindingsParams,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
let mut svc = context.seed.persistent_container.net_service.lock().await;
|
||||
svc.clear_bindings().await?;
|
||||
svc.clear_bindings(except.into_iter().collect()).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -165,7 +165,17 @@ pub async fn list_service_interfaces(
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub async fn clear_service_interfaces(context: EffectContext) -> Result<(), Error> {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ClearServiceInterfacesParams {
|
||||
pub except: Vec<ServiceInterfaceId>,
|
||||
}
|
||||
|
||||
pub async fn clear_service_interfaces(
|
||||
context: EffectContext,
|
||||
ClearServiceInterfacesParams { except }: ClearServiceInterfacesParams,
|
||||
) -> Result<(), Error> {
|
||||
let context = context.deref()?;
|
||||
let package_id = context.seed.id.clone();
|
||||
|
||||
@@ -179,7 +189,7 @@ pub async fn clear_service_interfaces(context: EffectContext) -> Result<(), Erro
|
||||
.as_idx_mut(&package_id)
|
||||
.or_not_found(&package_id)?
|
||||
.as_service_interfaces_mut()
|
||||
.ser(&Default::default())
|
||||
.mutate(|s| Ok(s.retain(|id, _| except.contains(id))))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ pub async fn get_store(
|
||||
callback,
|
||||
}: GetStoreParams,
|
||||
) -> Result<Value, Error> {
|
||||
dbg!(&callback);
|
||||
let context = context.deref()?;
|
||||
let peeked = context.seed.ctx.db.peek().await;
|
||||
let package_id = package_id.unwrap_or(context.seed.id.clone());
|
||||
@@ -33,8 +34,9 @@ pub async fn get_store(
|
||||
.as_private()
|
||||
.as_package_stores()
|
||||
.as_idx(&package_id)
|
||||
.or_not_found(&package_id)?
|
||||
.de()?;
|
||||
.map(|s| s.de())
|
||||
.transpose()?
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Some(callback) = callback {
|
||||
let callback = callback.register(&context.seed.persistent_container);
|
||||
@@ -45,10 +47,7 @@ pub async fn get_store(
|
||||
);
|
||||
}
|
||||
|
||||
Ok(path
|
||||
.get(&value)
|
||||
.ok_or_else(|| Error::new(eyre!("Did not find value at path"), ErrorKind::NotFound))?
|
||||
.clone())
|
||||
Ok(path.get(&value).cloned().unwrap_or_default())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::ffi::OsString;
|
||||
use std::io::IsTerminal;
|
||||
use std::ops::Deref;
|
||||
use std::os::unix::process::ExitStatusExt;
|
||||
use std::path::Path;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Stdio;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
use std::{ffi::OsString, path::PathBuf};
|
||||
|
||||
use axum::extract::ws::WebSocket;
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -15,7 +16,7 @@ use futures::stream::FusedStream;
|
||||
use futures::{SinkExt, StreamExt, TryStreamExt};
|
||||
use imbl_value::{json, InternedString};
|
||||
use itertools::Itertools;
|
||||
use models::{ImageId, PackageId, ProcedureName};
|
||||
use models::{ActionId, ImageId, PackageId, ProcedureName};
|
||||
use nix::sys::signal::Signal;
|
||||
use persistent_container::{PersistentContainer, Subcontainer};
|
||||
use rpc_toolkit::{from_fn_async, CallRemoteHandler, Empty, HandlerArgs, HandlerFor};
|
||||
@@ -39,6 +40,7 @@ use crate::prelude::*;
|
||||
use crate::progress::{NamedProgress, Progress};
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::service::action::update_requested_actions;
|
||||
use crate::service::service_map::InstallProgressHandles;
|
||||
use crate::util::actor::concurrent::ConcurrentActor;
|
||||
use crate::util::io::{create_file, AsyncReadStream};
|
||||
@@ -48,11 +50,9 @@ use crate::util::Never;
|
||||
use crate::volume::data_dir;
|
||||
use crate::CAP_1_KiB;
|
||||
|
||||
mod action;
|
||||
pub mod action;
|
||||
pub mod cli;
|
||||
mod config;
|
||||
mod control;
|
||||
mod dependencies;
|
||||
pub mod effects;
|
||||
pub mod persistent_container;
|
||||
mod properties;
|
||||
@@ -97,7 +97,7 @@ impl ServiceRef {
|
||||
.persistent_container
|
||||
.execute::<NoOutput>(
|
||||
Guid::new(),
|
||||
ProcedureName::Uninit,
|
||||
ProcedureName::PackageUninit,
|
||||
to_value(&target_version)?,
|
||||
None,
|
||||
) // TODO timeout
|
||||
@@ -257,7 +257,7 @@ impl Service {
|
||||
tokio::fs::create_dir_all(&path).await?;
|
||||
}
|
||||
}
|
||||
let start_stop = if i.as_status().as_main().de()?.running() {
|
||||
let start_stop = if i.as_status().de()?.running() {
|
||||
StartStop::Start
|
||||
} else {
|
||||
StartStop::Stop
|
||||
@@ -429,12 +429,13 @@ impl Service {
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
let procedure_id = Guid::new();
|
||||
service
|
||||
.seed
|
||||
.persistent_container
|
||||
.execute::<NoOutput>(
|
||||
Guid::new(),
|
||||
ProcedureName::Init,
|
||||
procedure_id.clone(),
|
||||
ProcedureName::PackageInit,
|
||||
to_value(&src_version)?,
|
||||
None,
|
||||
) // TODO timeout
|
||||
@@ -445,16 +446,60 @@ impl Service {
|
||||
progress.progress.complete();
|
||||
tokio::task::yield_now().await;
|
||||
}
|
||||
|
||||
let peek = ctx.db.peek().await;
|
||||
let mut action_input: BTreeMap<ActionId, Value> = BTreeMap::new();
|
||||
let requested_actions: BTreeSet<_> = peek
|
||||
.as_public()
|
||||
.as_package_data()
|
||||
.as_entries()?
|
||||
.into_iter()
|
||||
.map(|(_, pde)| {
|
||||
Ok(pde
|
||||
.as_requested_actions()
|
||||
.as_entries()?
|
||||
.into_iter()
|
||||
.map(|(_, r)| {
|
||||
Ok::<_, Error>(if r.as_request().as_package_id().de()? == manifest.id {
|
||||
Some(r.as_request().as_action_id().de()?)
|
||||
} else {
|
||||
None
|
||||
})
|
||||
})
|
||||
.filter_map_ok(|a| a))
|
||||
})
|
||||
.flatten_ok()
|
||||
.map(|a| a.and_then(|a| a))
|
||||
.try_collect()?;
|
||||
for action_id in requested_actions {
|
||||
if let Some(input) = service
|
||||
.get_action_input(procedure_id.clone(), action_id.clone())
|
||||
.await?
|
||||
.and_then(|i| i.value)
|
||||
{
|
||||
action_input.insert(action_id, input);
|
||||
}
|
||||
}
|
||||
ctx.db
|
||||
.mutate(|d| {
|
||||
let entry = d
|
||||
.mutate(|db| {
|
||||
for (action_id, input) in &action_input {
|
||||
for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
||||
pde.as_requested_actions_mut().mutate(|requested_actions| {
|
||||
Ok(update_requested_actions(
|
||||
requested_actions,
|
||||
&manifest.id,
|
||||
action_id,
|
||||
input,
|
||||
false,
|
||||
))
|
||||
})?;
|
||||
}
|
||||
}
|
||||
let entry = db
|
||||
.as_public_mut()
|
||||
.as_package_data_mut()
|
||||
.as_idx_mut(&manifest.id)
|
||||
.or_not_found(&manifest.id)?;
|
||||
if !manifest.has_config {
|
||||
entry.as_status_mut().as_configured_mut().ser(&true)?;
|
||||
}
|
||||
entry
|
||||
.as_state_info_mut()
|
||||
.ser(&PackageState::Installed(InstalledState { manifest }))?;
|
||||
|
||||
@@ -379,7 +379,11 @@ impl PersistentContainer {
|
||||
));
|
||||
}
|
||||
|
||||
self.rpc_client.request(rpc::Init, Empty {}).await?;
|
||||
self.rpc_client
|
||||
.request(rpc::Init, Empty {})
|
||||
.await
|
||||
.map_err(Error::from)
|
||||
.log_err();
|
||||
|
||||
self.state.send_modify(|s| s.rt_initialized = true);
|
||||
|
||||
@@ -548,7 +552,7 @@ impl PersistentContainer {
|
||||
impl Drop for PersistentContainer {
|
||||
fn drop(&mut self) {
|
||||
if let Some(destroy) = self.destroy() {
|
||||
tokio::spawn(async move { destroy.await.unwrap() });
|
||||
tokio::spawn(async move { destroy.await.log_err() });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Arc, Weak};
|
||||
use std::time::Duration;
|
||||
|
||||
use clap::builder::ValueParserFactory;
|
||||
use imbl::Vector;
|
||||
use imbl_value::Value;
|
||||
use models::ProcedureName;
|
||||
use models::{FromStrParser, ProcedureName};
|
||||
use rpc_toolkit::yajrc::RpcMethod;
|
||||
use rpc_toolkit::Empty;
|
||||
use ts_rs::TS;
|
||||
@@ -153,6 +155,11 @@ impl serde::Serialize for Sandbox {
|
||||
pub struct CallbackId(u64);
|
||||
impl CallbackId {
|
||||
pub fn register(self, container: &PersistentContainer) -> CallbackHandle {
|
||||
dbg!(eyre!(
|
||||
"callback {} registered for {}",
|
||||
self.0,
|
||||
container.s9pk.as_manifest().id
|
||||
));
|
||||
let this = Arc::new(self);
|
||||
let res = Arc::downgrade(&this);
|
||||
container
|
||||
@@ -161,6 +168,18 @@ impl CallbackId {
|
||||
CallbackHandle(res)
|
||||
}
|
||||
}
|
||||
impl FromStr for CallbackId {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
u64::from_str(s).map_err(Error::from).map(Self)
|
||||
}
|
||||
}
|
||||
impl ValueParserFactory for CallbackId {
|
||||
type Parser = FromStrParser<Self>;
|
||||
fn value_parser() -> Self::Parser {
|
||||
FromStrParser::new()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CallbackHandle(Weak<CallbackId>);
|
||||
impl CallbackHandle {
|
||||
|
||||
@@ -49,7 +49,7 @@ async fn service_actor_loop(
|
||||
.db
|
||||
.mutate(|d| {
|
||||
if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) {
|
||||
let previous = i.as_status().as_main().de()?;
|
||||
let previous = i.as_status().de()?;
|
||||
let main_status = match &kinds {
|
||||
ServiceStateKinds {
|
||||
transition_state: Some(TransitionKind::Restarting),
|
||||
@@ -89,7 +89,7 @@ async fn service_actor_loop(
|
||||
..
|
||||
} => MainStatus::Stopped,
|
||||
};
|
||||
i.as_status_mut().as_main_mut().ser(&main_status)?;
|
||||
i.as_status_mut().ser(&main_status)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -23,7 +23,7 @@ use crate::s9pk::manifest::PackageId;
|
||||
use crate::s9pk::merkle_archive::source::FileSource;
|
||||
use crate::s9pk::S9pk;
|
||||
use crate::service::{LoadDisposition, Service, ServiceRef};
|
||||
use crate::status::{MainStatus, Status};
|
||||
use crate::status::MainStatus;
|
||||
use crate::util::serde::Pem;
|
||||
|
||||
pub type DownloadInstallFuture = BoxFuture<'static, Result<InstallFuture, Error>>;
|
||||
@@ -174,16 +174,14 @@ impl ServiceMap {
|
||||
PackageState::Installing(installing)
|
||||
},
|
||||
data_version: None,
|
||||
status: Status {
|
||||
configured: false,
|
||||
main: MainStatus::Stopped,
|
||||
},
|
||||
status: MainStatus::Stopped,
|
||||
registry: None,
|
||||
developer_key: Pem::new(developer_key),
|
||||
icon,
|
||||
last_backup: None,
|
||||
current_dependencies: Default::default(),
|
||||
actions: Default::default(),
|
||||
requested_actions: Default::default(),
|
||||
service_interfaces: Default::default(),
|
||||
hosts: Default::default(),
|
||||
store_exposed_dependents: Default::default(),
|
||||
|
||||
@@ -9,8 +9,7 @@ use super::TempDesiredRestore;
|
||||
use crate::disk::mount::filesystem::ReadWrite;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::config::GetConfig;
|
||||
use crate::service::dependencies::DependencyConfig;
|
||||
use crate::service::action::GetActionInput;
|
||||
use crate::service::transition::{TransitionKind, TransitionState};
|
||||
use crate::service::ServiceActor;
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
@@ -23,9 +22,7 @@ pub(in crate::service) struct Backup {
|
||||
impl Handler<Backup> for ServiceActor {
|
||||
type Response = Result<BoxFuture<'static, Result<(), Error>>, Error>;
|
||||
fn conflicts_with(_: &Backup) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::everything()
|
||||
.except::<GetConfig>()
|
||||
.except::<DependencyConfig>()
|
||||
ConflictBuilder::everything().except::<GetActionInput>()
|
||||
}
|
||||
async fn handle(
|
||||
&mut self,
|
||||
|
||||
@@ -3,8 +3,7 @@ use futures::FutureExt;
|
||||
use super::TempDesiredRestore;
|
||||
use crate::prelude::*;
|
||||
use crate::rpc_continuations::Guid;
|
||||
use crate::service::config::GetConfig;
|
||||
use crate::service::dependencies::DependencyConfig;
|
||||
use crate::service::action::GetActionInput;
|
||||
use crate::service::transition::{TransitionKind, TransitionState};
|
||||
use crate::service::{Service, ServiceActor};
|
||||
use crate::util::actor::background::BackgroundJobQueue;
|
||||
@@ -15,9 +14,7 @@ pub(super) struct Restart;
|
||||
impl Handler<Restart> for ServiceActor {
|
||||
type Response = ();
|
||||
fn conflicts_with(_: &Restart) -> ConflictBuilder<Self> {
|
||||
ConflictBuilder::everything()
|
||||
.except::<GetConfig>()
|
||||
.except::<DependencyConfig>()
|
||||
ConflictBuilder::everything().except::<GetActionInput>()
|
||||
}
|
||||
async fn handle(&mut self, _: Guid, _: Restart, jobs: &BackgroundJobQueue) -> Self::Response {
|
||||
// So Need a handle to just a single field in the state
|
||||
|
||||
Reference in New Issue
Block a user