implement package.action rpc/cli command (#503)

* implement package.action rpc/cli command

* use action id instead of name

* make ActionResult display impl nicer

Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
Chris Guida
2021-09-24 20:51:02 -05:00
committed by Aiden McClelland
parent 3dfecb88c4
commit 80def81fbb
2 changed files with 80 additions and 10 deletions

View File

@@ -1,16 +1,19 @@
use std::path::Path; use std::path::Path;
use std::str::FromStr;
use anyhow::anyhow; use anyhow::anyhow;
use clap::ArgMatches;
use indexmap::{IndexMap, IndexSet}; use indexmap::{IndexMap, IndexSet};
use patch_db::HasModel; use patch_db::HasModel;
use rpc_toolkit::command;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use self::docker::DockerAction; use self::docker::DockerAction;
use crate::config::{Config, ConfigSpec}; use crate::config::{Config, ConfigSpec};
use crate::context::RpcContext; use crate::context::RpcContext;
use crate::id::Id; use crate::id::{Id, InvalidId};
use crate::s9pk::manifest::PackageId; 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::volume::Volumes;
use crate::{Error, ResultExt}; use crate::{Error, ResultExt};
@@ -20,6 +23,17 @@ pub mod docker;
#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)] #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize)]
pub struct ActionId<S: AsRef<str> = String>(Id<S>); pub struct ActionId<S: AsRef<str> = String>(Id<S>);
impl FromStr for ActionId {
type Err = InvalidId;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(ActionId(Id::try_from(s.to_owned())?))
}
}
impl From<ActionId> for String {
fn from(value: ActionId) -> Self {
value.0.into()
}
}
impl<S: AsRef<str>> AsRef<ActionId<S>> for ActionId<S> { impl<S: AsRef<str>> AsRef<ActionId<S>> for ActionId<S> {
fn as_ref(&self) -> &ActionId<S> { fn as_ref(&self) -> &ActionId<S> {
self self
@@ -56,14 +70,14 @@ where
#[derive(Clone, Debug, Default, Deserialize, Serialize)] #[derive(Clone, Debug, Default, Deserialize, Serialize)]
pub struct Actions(pub IndexMap<ActionId, Action>); pub struct Actions(pub IndexMap<ActionId, Action>);
#[derive(Debug, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "version")] #[serde(tag = "version")]
pub enum ActionResult { pub enum ActionResult {
#[serde(rename = "0")] #[serde(rename = "0")]
V0(ActionResultV0), V0(ActionResultV0),
} }
#[derive(Debug, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
pub struct ActionResultV0 { pub struct ActionResultV0 {
pub message: String, pub message: String,
pub value: ValuePrimative, pub value: ValuePrimative,
@@ -96,20 +110,23 @@ impl Action {
ctx: &RpcContext, ctx: &RpcContext,
pkg_id: &PackageId, pkg_id: &PackageId,
pkg_version: &Version, pkg_version: &Version,
action_id: &ActionId,
volumes: &Volumes, volumes: &Volumes,
input: Config, input: Option<Config>,
) -> Result<ActionResult, Error> { ) -> Result<ActionResult, Error> {
self.input_spec if let Some(ref input) = input {
.matches(&input) self.input_spec
.with_kind(crate::ErrorKind::ConfigSpecViolation)?; .matches(&input)
.with_kind(crate::ErrorKind::ConfigSpecViolation)?;
}
self.implementation self.implementation
.execute( .execute(
ctx, ctx,
pkg_id, pkg_id,
pkg_version, pkg_version,
Some(&format!("{}Action", self.name)), Some(&format!("{}Action", action_id)),
volumes, volumes,
Some(input), input,
true, true,
) )
.await? .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<Config>,
#[allow(unused_variables)]
#[arg(long = "format")]
format: Option<IoFormat>,
) -> Result<ActionResult, Error> {
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; pub struct NoOutput;
impl<'de> Deserialize<'de> for NoOutput { impl<'de> Deserialize<'de> for NoOutput {
fn deserialize<D>(_: D) -> Result<Self, D::Error> fn deserialize<D>(_: D) -> Result<Self, D::Error>

View File

@@ -84,6 +84,7 @@ pub fn server() -> Result<(), RpcError> {
} }
#[command(subcommands( #[command(subcommands(
action::action,
install::install, install::install,
install::uninstall, install::uninstall,
config::config, config::config,