crates/monetize-server/src/services/plugins.rs
//! **`Plugins`** — the operator's "Update plugins" ([`crate::plugins`]). Thin: it checks the
//! operator, queues the job on the server's jera lane and answers at once; `Status` reads
//! where a job stands.
use std::sync::Arc;
use monetize_proto as pb;
use monetize_proto::plugins_server::Plugins;
use tonic::{Request, Response, Status};
use crate::plugins::JobView;
use crate::state::AppState;
pub struct PluginsService(pub Arc<AppState>);
/// The operator's alone: the operator marker, never a site's own bearer.
fn operator_only<T>(req: &Request<T>) -> Result<(), Status> {
if req.extensions().get::<crate::auth::Operator>().is_some()
&& req
.extensions()
.get::<crate::auth::CallerEstate>()
.is_none()
{
Ok(())
} else {
Err(Status::permission_denied(
"[plugins-operator] updating this box's plugins is the operator's alone",
))
}
}
/// Pure: a job on the wire.
pub fn job_out(v: JobView) -> pb::PluginUpdateJob {
pb::PluginUpdateJob {
job: v.job,
state: v.state,
apply: v.apply,
rows: v
.rows
.into_iter()
.map(|r| pb::PluginRow {
name: r.name,
role: r.role,
outcome: r.outcome,
detail: r.detail,
blake3: r.blake3,
})
.collect(),
error: v.error,
}
}
#[tonic::async_trait]
impl Plugins for PluginsService {
async fn update(
&self,
req: Request<pb::PluginUpdateRequest>,
) -> Result<Response<pb::PluginUpdateJob>, Status> {
operator_only(&req)?;
let apply = req.into_inner().apply;
let v = self
.0
.plugin_jobs
.submit(&self.0, apply)
.map_err(Status::unavailable)?;
Ok(Response::new(job_out(v)))
}
async fn status(
&self,
req: Request<pb::PluginUpdateStatusRequest>,
) -> Result<Response<pb::PluginUpdateJob>, Status> {
operator_only(&req)?;
let job = req.into_inner().job;
let v = self.0.plugin_jobs.status(&job).ok_or_else(|| {
Status::not_found(format!(
"[plugins-no-job] no plugin update {}",
if job.trim().is_empty() {
"has run on this server yet".to_string()
} else {
format!("`{}`", job.trim())
}
))
})?;
Ok(Response::new(job_out(v)))
}
}