diff --git a/core/startos/src/action.rs b/core/startos/src/action.rs index 57a5121b2..200128d61 100644 --- a/core/startos/src/action.rs +++ b/core/startos/src/action.rs @@ -93,7 +93,7 @@ impl fmt::Display for ActionResultV0 { } } -fn display_action_result(params: WithIoFormat, result: Option) { +pub fn display_action_result(params: WithIoFormat, result: Option) { let Some(result) = result else { return; }; @@ -115,7 +115,7 @@ pub struct RunActionParams { pub input: Option, } -pub fn action() -> ParentHandler { +pub fn action_api() -> ParentHandler { ParentHandler::new() .subcommand( "get-input", diff --git a/core/startos/src/db/model/package.rs b/core/startos/src/db/model/package.rs index dec4e06b6..75c099722 100644 --- a/core/startos/src/db/model/package.rs +++ b/core/startos/src/db/model/package.rs @@ -327,12 +327,14 @@ pub struct ActionMetadata { #[serde(default)] pub visibility: ActionVisibility, pub allowed_statuses: AllowedStatuses, + pub has_input: bool, pub group: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, TS)] #[ts(export)] #[serde(rename_all = "kebab-case")] +#[serde(rename_all_fields = "camelCase")] pub enum ActionVisibility { Hidden, Disabled { reason: String }, @@ -361,8 +363,8 @@ pub struct PackageDataEntry { pub last_backup: Option>, pub current_dependencies: CurrentDependencies, pub actions: BTreeMap, - #[ts(as = "BTreeMap::")] - pub requested_actions: BTreeMap, + #[ts(as = "BTreeMap::")] + pub requested_actions: BTreeMap, pub service_interfaces: BTreeMap, pub hosts: Hosts, #[ts(type = "string[]")] @@ -409,8 +411,8 @@ pub struct CurrentDependencyInfo { pub kind: CurrentDependencyKind, #[ts(type = "string")] pub version_range: VersionRange, - #[ts(as = "BTreeMap::")] - pub requested_actions: BTreeMap, + #[ts(as = "BTreeMap::")] + pub requested_actions: BTreeMap, } impl CurrentDependencyInfo { pub fn update(&mut self, new: Self) { @@ -443,14 +445,29 @@ impl Default for CurrentDependencyKind { } } +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +pub struct ActionRequestEntry { + pub request: ActionRequest, + pub active: bool, +} + #[derive(Clone, Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] pub struct ActionRequest { pub id: ActionId, - pub r#if: Option, + pub when: Option, pub input: Option, } +#[derive(Clone, Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +pub struct ActionRequestTrigger { + #[serde(default)] + pub once: bool, + pub condition: ActionRequestCondition, +} + #[derive(Clone, Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "kebab-case")] pub enum ActionRequestCondition { diff --git a/core/startos/src/lib.rs b/core/startos/src/lib.rs index 40b489027..9268e65f5 100644 --- a/core/startos/src/lib.rs +++ b/core/startos/src/lib.rs @@ -238,7 +238,7 @@ pub fn server() -> ParentHandler { pub fn package() -> ParentHandler { ParentHandler::new() - .subcommand("action", action::action::()) + .subcommand("action", action::action_api::()) .subcommand( "install", from_fn_async(install::install) diff --git a/core/startos/src/service/effects/action.rs b/core/startos/src/service/effects/action.rs index 50b95fd4c..e4a32bf68 100644 --- a/core/startos/src/service/effects/action.rs +++ b/core/startos/src/service/effects/action.rs @@ -1,22 +1,50 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::BTreeSet; use models::{ActionId, PackageId}; +use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; -use crate::action::ActionResult; +use crate::action::{display_action_result, ActionInput, ActionResult}; use crate::db::model::package::ActionMetadata; use crate::rpc_continuations::Guid; +use crate::service::cli::ContainerCliContext; use crate::service::effects::prelude::*; +use crate::util::serde::HandlerExtSerde; + +pub fn action_api() -> ParentHandler { + ParentHandler::new() + .subcommand("export", from_fn_async(export_action).no_cli()) + .subcommand( + "clear", + from_fn_async(clear_actions) + .no_display() + .with_call_remote::(), + ) + .subcommand( + "get-input", + from_fn_async(get_action_input) + .with_display_serializable() + .with_call_remote::(), + ) + .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::(), + ) +} #[derive(Debug, Clone, Serialize, Deserialize, TS)] #[ts(export)] #[serde(rename_all = "camelCase")] pub struct ExportActionParams { - #[ts(optional)] - package_id: Option, 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,10 +59,7 @@ 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?; @@ -49,7 +74,7 @@ pub struct ClearActionsParams { pub except: Vec, } -pub async fn clear_actions( +async fn clear_actions( context: EffectContext, ClearActionsParams { except }: ClearActionsParams, ) -> Result<(), Error> { @@ -72,12 +97,51 @@ pub async fn clear_actions( Ok(()) } -#[derive(Debug, Clone, Serialize, Deserialize, TS)] +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct GetActionInputParams { + #[serde(default)] + #[ts(skip)] + #[arg(skip)] + procedure_id: Guid, + #[ts(optional)] + package_id: Option, + action_id: ActionId, +} +async fn get_action_input( + context: EffectContext, + GetActionInputParams { + procedure_id, + package_id, + action_id, + }: GetActionInputParams, +) -> Result, Error> { + let context = context.deref()?; + + if let Some(package_id) = package_id { + context + .seed + .ctx + .services + .get(&package_id) + .await + .as_ref() + .or_not_found(&package_id)? + .get_action_input(procedure_id, action_id) + .await + } else { + context.get_action_input(procedure_id, action_id).await + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)] #[serde(rename_all = "camelCase")] #[ts(export)] -pub struct ExecuteAction { +pub struct RunActionParams { #[serde(default)] #[ts(skip)] + #[arg(skip)] procedure_id: Guid, #[ts(optional)] package_id: Option, @@ -85,14 +149,14 @@ pub struct ExecuteAction { #[ts(type = "any")] input: Value, } -pub async fn execute_action( +async fn run_action( context: EffectContext, - ExecuteAction { + RunActionParams { procedure_id, package_id, action_id, input, - }: ExecuteAction, + }: RunActionParams, ) -> Result, Error> { let context = context.deref()?; diff --git a/core/startos/src/service/effects/dependency.rs b/core/startos/src/service/effects/dependency.rs index 921646e4b..8297f17a5 100644 --- a/core/startos/src/service/effects/dependency.rs +++ b/core/startos/src/service/effects/dependency.rs @@ -11,7 +11,8 @@ use patch_db::json_ptr::JsonPointer; use tokio::process::Command; use crate::db::model::package::{ - ActionRequest, CurrentDependencyInfo, CurrentDependencyKind, ManifestPreference, + ActionRequest, ActionRequestEntry, CurrentDependencyInfo, CurrentDependencyKind, + ManifestPreference, }; use crate::disk::mount::filesystem::bind::Bind; use crate::disk::mount::filesystem::idmapped::IdMapped; @@ -321,8 +322,8 @@ pub struct CheckDependenciesResult { #[ts(type = "string[]")] satisfies: BTreeSet, is_running: bool, - #[ts(as = "BTreeMap::")] - requested_actions: BTreeMap, + #[ts(as = "BTreeMap::")] + requested_actions: BTreeMap, #[ts(as = "BTreeMap::")] health_checks: OrdMap, } diff --git a/core/startos/src/service/effects/mod.rs b/core/startos/src/service/effects/mod.rs index 115bc032a..844f5646b 100644 --- a/core/startos/src/service/effects/mod.rs +++ b/core/startos/src/service/effects/mod.rs @@ -25,18 +25,7 @@ pub fn handler() -> ParentHandler { from_fn(echo::).with_call_remote::(), ) // 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::()) // callbacks .subcommand( "clear-callbacks", diff --git a/sdk/lib/osBindings/ActionMetadata.ts b/sdk/lib/osBindings/ActionMetadata.ts index 318ac4059..ade129fd4 100644 --- a/sdk/lib/osBindings/ActionMetadata.ts +++ b/sdk/lib/osBindings/ActionMetadata.ts @@ -8,5 +8,6 @@ export type ActionMetadata = { warning: string | null visibility: ActionVisibility allowedStatuses: AllowedStatuses + hasInput: boolean group: string | null } diff --git a/sdk/lib/osBindings/ActionRequest.ts b/sdk/lib/osBindings/ActionRequest.ts index 81f1e490b..c0dbbae18 100644 --- a/sdk/lib/osBindings/ActionRequest.ts +++ b/sdk/lib/osBindings/ActionRequest.ts @@ -1,10 +1,10 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ActionId } from "./ActionId" -import type { ActionRequestCondition } from "./ActionRequestCondition" import type { ActionRequestInput } from "./ActionRequestInput" +import type { ActionRequestTrigger } from "./ActionRequestTrigger" export type ActionRequest = { id: ActionId - if: ActionRequestCondition | null + when: ActionRequestTrigger | null input: ActionRequestInput | null } diff --git a/sdk/lib/osBindings/ActionRequestEntry.ts b/sdk/lib/osBindings/ActionRequestEntry.ts new file mode 100644 index 000000000..0e716abe4 --- /dev/null +++ b/sdk/lib/osBindings/ActionRequestEntry.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionRequest } from "./ActionRequest" + +export type ActionRequestEntry = { request: ActionRequest; active: boolean } diff --git a/sdk/lib/osBindings/ActionRequestTrigger.ts b/sdk/lib/osBindings/ActionRequestTrigger.ts new file mode 100644 index 000000000..ebd0963e5 --- /dev/null +++ b/sdk/lib/osBindings/ActionRequestTrigger.ts @@ -0,0 +1,7 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionRequestCondition } from "./ActionRequestCondition" + +export type ActionRequestTrigger = { + once: boolean + condition: ActionRequestCondition +} diff --git a/sdk/lib/osBindings/CheckDependenciesResult.ts b/sdk/lib/osBindings/CheckDependenciesResult.ts index f86166856..8a291a2e5 100644 --- a/sdk/lib/osBindings/CheckDependenciesResult.ts +++ b/sdk/lib/osBindings/CheckDependenciesResult.ts @@ -1,5 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ActionRequest } from "./ActionRequest" +import type { ActionRequestEntry } from "./ActionRequestEntry" import type { HealthCheckId } from "./HealthCheckId" import type { NamedHealthCheckResult } from "./NamedHealthCheckResult" import type { PackageId } from "./PackageId" @@ -10,6 +10,6 @@ export type CheckDependenciesResult = { installedVersion: string | null satisfies: string[] isRunning: boolean - requestedActions: { [key: string]: ActionRequest } + requestedActions: { [key: string]: ActionRequestEntry } healthChecks: { [key: HealthCheckId]: NamedHealthCheckResult } } diff --git a/sdk/lib/osBindings/CurrentDependencyInfo.ts b/sdk/lib/osBindings/CurrentDependencyInfo.ts index 8a29720a7..3157b809b 100644 --- a/sdk/lib/osBindings/CurrentDependencyInfo.ts +++ b/sdk/lib/osBindings/CurrentDependencyInfo.ts @@ -1,12 +1,12 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { ActionRequest } from "./ActionRequest" +import type { ActionRequestEntry } from "./ActionRequestEntry" import type { DataUrl } from "./DataUrl" export type CurrentDependencyInfo = { title: string | null icon: DataUrl | null versionRange: string - requestedActions: { [key: string]: ActionRequest } + requestedActions: { [key: string]: ActionRequestEntry } } & ( | { kind: "optional" } | { kind: "exists" } diff --git a/sdk/lib/osBindings/ExportActionParams.ts b/sdk/lib/osBindings/ExportActionParams.ts index 8bcfbc349..d4aa33102 100644 --- a/sdk/lib/osBindings/ExportActionParams.ts +++ b/sdk/lib/osBindings/ExportActionParams.ts @@ -1,10 +1,5 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ActionId } from "./ActionId" import type { ActionMetadata } from "./ActionMetadata" -import type { PackageId } from "./PackageId" -export type ExportActionParams = { - packageId?: PackageId - id: ActionId - metadata: ActionMetadata -} +export type ExportActionParams = { id: ActionId; metadata: ActionMetadata } diff --git a/sdk/lib/osBindings/GetActionInputParams.ts b/sdk/lib/osBindings/GetActionInputParams.ts new file mode 100644 index 000000000..568ceb907 --- /dev/null +++ b/sdk/lib/osBindings/GetActionInputParams.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { ActionId } from "./ActionId" +import type { PackageId } from "./PackageId" + +export type GetActionInputParams = { packageId?: PackageId; actionId: ActionId } diff --git a/sdk/lib/osBindings/PackageDataEntry.ts b/sdk/lib/osBindings/PackageDataEntry.ts index 172740d7e..86df7b767 100644 --- a/sdk/lib/osBindings/PackageDataEntry.ts +++ b/sdk/lib/osBindings/PackageDataEntry.ts @@ -1,7 +1,7 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { ActionId } from "./ActionId" import type { ActionMetadata } from "./ActionMetadata" -import type { ActionRequest } from "./ActionRequest" +import type { ActionRequestEntry } from "./ActionRequestEntry" import type { CurrentDependencies } from "./CurrentDependencies" import type { DataUrl } from "./DataUrl" import type { Hosts } from "./Hosts" @@ -21,7 +21,7 @@ export type PackageDataEntry = { lastBackup: string | null currentDependencies: CurrentDependencies actions: { [key: ActionId]: ActionMetadata } - requestedActions: { [key: string]: ActionRequest } + requestedActions: { [key: string]: ActionRequestEntry } serviceInterfaces: { [key: ServiceInterfaceId]: ServiceInterface } hosts: Hosts storeExposedDependents: string[] diff --git a/sdk/lib/osBindings/ExecuteAction.ts b/sdk/lib/osBindings/RunActionParams.ts similarity index 88% rename from sdk/lib/osBindings/ExecuteAction.ts rename to sdk/lib/osBindings/RunActionParams.ts index 6e3c44f79..33864d1e6 100644 --- a/sdk/lib/osBindings/ExecuteAction.ts +++ b/sdk/lib/osBindings/RunActionParams.ts @@ -2,7 +2,7 @@ import type { ActionId } from "./ActionId" import type { PackageId } from "./PackageId" -export type ExecuteAction = { +export type RunActionParams = { packageId?: PackageId actionId: ActionId input: any diff --git a/sdk/lib/osBindings/index.ts b/sdk/lib/osBindings/index.ts index 0c779d15a..d8235d022 100644 --- a/sdk/lib/osBindings/index.ts +++ b/sdk/lib/osBindings/index.ts @@ -3,7 +3,9 @@ export { ActionId } from "./ActionId" export { ActionInput } from "./ActionInput" export { ActionMetadata } from "./ActionMetadata" export { ActionRequestCondition } from "./ActionRequestCondition" +export { ActionRequestEntry } from "./ActionRequestEntry" export { ActionRequestInput } from "./ActionRequestInput" +export { ActionRequestTrigger } from "./ActionRequestTrigger" export { ActionRequest } from "./ActionRequest" export { ActionVisibility } from "./ActionVisibility" export { AddAdminParams } from "./AddAdminParams" @@ -54,12 +56,12 @@ export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams" export { Duration } from "./Duration" export { EchoParams } from "./EchoParams" export { EncryptedWire } from "./EncryptedWire" -export { ExecuteAction } from "./ExecuteAction" export { ExportActionParams } from "./ExportActionParams" export { ExportServiceInterfaceParams } from "./ExportServiceInterfaceParams" export { ExposeForDependentsParams } from "./ExposeForDependentsParams" export { FullIndex } from "./FullIndex" export { FullProgress } from "./FullProgress" +export { GetActionInputParams } from "./GetActionInputParams" export { GetHostInfoParams } from "./GetHostInfoParams" export { GetOsAssetParams } from "./GetOsAssetParams" export { GetOsVersionParams } from "./GetOsVersionParams" @@ -129,6 +131,7 @@ export { RegistryAsset } from "./RegistryAsset" export { RegistryInfo } from "./RegistryInfo" export { RemoveVersionParams } from "./RemoveVersionParams" export { RequestCommitment } from "./RequestCommitment" +export { RunActionParams } from "./RunActionParams" export { Security } from "./Security" export { ServerInfo } from "./ServerInfo" export { ServerSpecs } from "./ServerSpecs"