diff --git a/appmgr/src/action/mod.rs b/appmgr/src/action/mod.rs index 52ae1b82d..4ddd40794 100644 --- a/appmgr/src/action/mod.rs +++ b/appmgr/src/action/mod.rs @@ -1,16 +1,19 @@ use std::path::Path; +use std::str::FromStr; use anyhow::anyhow; +use clap::ArgMatches; use indexmap::{IndexMap, IndexSet}; use patch_db::HasModel; +use rpc_toolkit::command; use serde::{Deserialize, Serialize}; use self::docker::DockerAction; use crate::config::{Config, ConfigSpec}; use crate::context::RpcContext; -use crate::id::Id; +use crate::id::{Id, InvalidId}; use crate::s9pk::manifest::PackageId; -use crate::util::{ValuePrimative, Version}; +use crate::util::{IoFormat, ValuePrimative, Version, display_serializable, parse_stdin_deserializable}; use crate::volume::Volumes; use crate::{Error, ResultExt}; @@ -20,6 +23,17 @@ pub mod docker; #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] pub struct ActionId = String>(Id); +impl FromStr for ActionId { + type Err = InvalidId; + fn from_str(s: &str) -> Result { + Ok(ActionId(Id::try_from(s.to_owned())?)) + } +} +impl From for String { + fn from(value: ActionId) -> Self { + value.0.into() + } +} impl> AsRef> for ActionId { fn as_ref(&self) -> &ActionId { self @@ -56,14 +70,14 @@ where #[derive(Clone, Debug, Default, Deserialize, Serialize)] pub struct Actions(pub IndexMap); -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(tag = "version")] pub enum ActionResult { #[serde(rename = "0")] V0(ActionResultV0), } -#[derive(Debug, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct ActionResultV0 { pub message: String, pub value: ValuePrimative, @@ -96,20 +110,23 @@ impl Action { ctx: &RpcContext, pkg_id: &PackageId, pkg_version: &Version, + action_id: &ActionId, volumes: &Volumes, - input: Config, + input: Option, ) -> Result { - self.input_spec - .matches(&input) - .with_kind(crate::ErrorKind::ConfigSpecViolation)?; + if let Some(ref input) = input { + self.input_spec + .matches(&input) + .with_kind(crate::ErrorKind::ConfigSpecViolation)?; + } self.implementation .execute( ctx, pkg_id, pkg_version, - Some(&format!("{}Action", self.name)), + Some(&format!("{}Action", action_id)), volumes, - Some(input), + input, true, ) .await? @@ -160,6 +177,58 @@ impl ActionImplementation { } } +fn display_action_result(action_result: ActionResult, matches: &ArgMatches<'_>) { + if matches.is_present("format") { + return display_serializable(action_result, matches); + } + match action_result { + ActionResult::V0(ar) => { + println!("{}: {}", ar.message, serde_json::to_string(&ar.value).unwrap()); + }, + } +} + +#[command(about = "Executes an action", display(display_action_result))] +pub async fn action( + #[context] ctx: RpcContext, + #[arg(rename = "id")] pkg_id: PackageId, + #[arg(rename = "action-id")] action_id: ActionId, + #[arg(stdin, parse(parse_stdin_deserializable))] input: Option, + #[allow(unused_variables)] + #[arg(long = "format")] + format: Option, +) -> Result { + let mut db = ctx.db.handle(); + let manifest = crate::db::DatabaseModel::new() + .package_data() + .idx_model(&pkg_id) + .and_then(|p| p.installed()) + .expect(&mut db) + .await + .with_kind(crate::ErrorKind::NotFound)? + .manifest() + .get(&mut db, true) + .await? + .to_owned(); + if let Some(action) = manifest.actions.0.get(&action_id) { + action + .execute( + &ctx, + &manifest.id, + &manifest.version, + &action_id, + &manifest.volumes, + input, + ) + .await + } else { + Err(Error::new( + anyhow!("Action not found in manifest"), + crate::ErrorKind::NotFound, + )) + } +} + pub struct NoOutput; impl<'de> Deserialize<'de> for NoOutput { fn deserialize(_: D) -> Result diff --git a/appmgr/src/lib.rs b/appmgr/src/lib.rs index 24119481f..b2ce31152 100644 --- a/appmgr/src/lib.rs +++ b/appmgr/src/lib.rs @@ -84,6 +84,7 @@ pub fn server() -> Result<(), RpcError> { } #[command(subcommands( + action::action, install::install, install::uninstall, config::config,