mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
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:
committed by
Aiden McClelland
parent
3dfecb88c4
commit
80def81fbb
@@ -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> {
|
||||||
|
if let Some(ref input) = input {
|
||||||
self.input_spec
|
self.input_spec
|
||||||
.matches(&input)
|
.matches(&input)
|
||||||
.with_kind(crate::ErrorKind::ConfigSpecViolation)?;
|
.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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user