mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
210 lines
6.2 KiB
Rust
210 lines
6.2 KiB
Rust
use std::collections::BTreeMap;
|
|
use std::time::Duration;
|
|
|
|
use imbl_value::json;
|
|
|
|
use crate::action::{ActionInput, ActionResult};
|
|
use crate::db::model::package::{
|
|
ActionVisibility, AllowedStatuses, TaskCondition, TaskEntry, TaskInput, TaskSeverity,
|
|
};
|
|
use crate::prelude::*;
|
|
use crate::rpc_continuations::Guid;
|
|
use crate::service::{ProcedureName, Service, ServiceActor};
|
|
use crate::util::actor::background::BackgroundJobQueue;
|
|
use crate::util::actor::{ConflictBuilder, Handler};
|
|
use crate::util::serde::is_partial_of;
|
|
use crate::{ActionId, PackageId, ReplayId};
|
|
|
|
pub(super) struct GetActionInput {
|
|
id: ActionId,
|
|
}
|
|
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,
|
|
GetActionInput { id: action_id }: GetActionInput,
|
|
_: &BackgroundJobQueue,
|
|
) -> Self::Response {
|
|
let container = &self.0.persistent_container;
|
|
container
|
|
.execute::<Option<ActionInput>>(
|
|
id,
|
|
ProcedureName::GetActionInput(action_id),
|
|
Value::Null,
|
|
Some(Duration::from_secs(30)),
|
|
)
|
|
.await
|
|
.with_kind(ErrorKind::Action)
|
|
}
|
|
}
|
|
|
|
impl Service {
|
|
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_tasks(
|
|
tasks: &mut BTreeMap<ReplayId, TaskEntry>,
|
|
package_id: &PackageId,
|
|
action_id: &ActionId,
|
|
input: &Value,
|
|
was_run: bool,
|
|
) -> bool {
|
|
let mut critical_activated = false;
|
|
tasks.retain(|_, v| {
|
|
if &v.task.package_id != package_id || &v.task.action_id != action_id {
|
|
return true;
|
|
}
|
|
if let Some(when) = &v.task.when {
|
|
match &when.condition {
|
|
TaskCondition::InputNotMatches => match &v.task.input {
|
|
Some(TaskInput::Partial { value }) => {
|
|
if is_partial_of(value, input) {
|
|
if when.once {
|
|
return !was_run;
|
|
} else {
|
|
v.active = false;
|
|
}
|
|
} else {
|
|
v.active = true;
|
|
if v.task.severity == TaskSeverity::Critical {
|
|
critical_activated = true;
|
|
}
|
|
}
|
|
}
|
|
None => {
|
|
tracing::error!("action request exists in an invalid state {:?}", v.task);
|
|
}
|
|
},
|
|
}
|
|
true
|
|
} else {
|
|
!was_run
|
|
}
|
|
});
|
|
critical_activated
|
|
}
|
|
|
|
pub(super) struct RunAction {
|
|
action_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 {
|
|
ref action_id,
|
|
input,
|
|
}: RunAction,
|
|
_: &BackgroundJobQueue,
|
|
) -> Self::Response {
|
|
let container = &self.0.persistent_container;
|
|
let package_id = &self.0.id;
|
|
let pde = self
|
|
.0
|
|
.ctx
|
|
.db
|
|
.peek()
|
|
.await
|
|
.into_public()
|
|
.into_package_data()
|
|
.into_idx(package_id)
|
|
.or_not_found(package_id)?;
|
|
let action = pde
|
|
.as_actions()
|
|
.as_idx(action_id)
|
|
.or_not_found(lazy_format!("{package_id} action {action_id}"))?
|
|
.de()?;
|
|
if matches!(&action.visibility, ActionVisibility::Disabled(_)) {
|
|
return Err(Error::new(
|
|
eyre!("action {action_id} is disabled"),
|
|
ErrorKind::Action,
|
|
));
|
|
}
|
|
let running = pde.as_status_info().as_started().transpose_ref().is_some();
|
|
if match action.allowed_statuses {
|
|
AllowedStatuses::OnlyRunning => !running,
|
|
AllowedStatuses::OnlyStopped => running,
|
|
_ => false,
|
|
} {
|
|
return Err(Error::new(
|
|
eyre!("service is not in allowed status for {action_id}"),
|
|
ErrorKind::Action,
|
|
));
|
|
}
|
|
let result = container
|
|
.execute::<Option<ActionResult>>(
|
|
id.clone(),
|
|
ProcedureName::RunAction(action_id.clone()),
|
|
json!({
|
|
"input": input,
|
|
}),
|
|
Some(Duration::from_secs(30)),
|
|
)
|
|
.await
|
|
.with_kind(ErrorKind::Action)?;
|
|
let package_id = package_id.clone();
|
|
self.0
|
|
.ctx
|
|
.db
|
|
.mutate(|db| {
|
|
for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
|
if pde.as_tasks_mut().mutate(|tasks| {
|
|
Ok(update_tasks(tasks, &package_id, action_id, &input, true))
|
|
})? {
|
|
pde.as_status_info_mut().stop()?;
|
|
}
|
|
}
|
|
Ok(())
|
|
})
|
|
.await
|
|
.result?;
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
impl Service {
|
|
pub async fn run_action(
|
|
&self,
|
|
id: Guid,
|
|
action_id: ActionId,
|
|
input: Value,
|
|
) -> Result<Option<ActionResult>, Error> {
|
|
self.actor.send(id, RunAction { action_id, input }).await?
|
|
}
|
|
}
|