Skip to content

Commit

Permalink
Merge pull request #26 from tinythings/isbm-handler-bridge
Browse files Browse the repository at this point in the history
Add handler bridge
  • Loading branch information
isbm authored Oct 24, 2024
2 parents f231c09 + 49e877f commit 7ad17df
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 12 deletions.
1 change: 1 addition & 0 deletions docs/evthandlers/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Event Handlers

console_logger
outcome_logger
pipescript

Overview
--------
Expand Down
80 changes: 80 additions & 0 deletions docs/evthandlers/pipescript.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
Pipe Script
===========

.. note::

This document explains how to use **pipescript** event handler.

Synopsis
--------

.. code-block:: text
:caption: Initialisation
handler:
- pipescript
*Pipescript* handler is used to pipe action's response through any script, using STDIN.
User can define what to do with this information further.

.. important::

This handler will react only if action contains return code ``0``, i.e. handler has
a proper response data structure. Otherwise handler will skip the process and
will log an error.

Options
-------

``program``
^^^^^^^^^^

A full command line what needs to be called in event of writing STDIN to the program. Example:

.. code-block:: yaml
:caption: Program definition
program: "/path/to/my/script.pl --some=argument --quiet"
``quiet``
^^^^^^^^^^^

**Optional.** Mute logging. Example:

.. code-block:: yaml
:caption: Mute logging
quiet: true
``format``
^^^^^^^^^^

In what format output needs to be sent to the target program. Options:

- ``yaml``
- ``json``

Example:

.. code-block:: yaml
:caption: Format definition
format: json # or "yaml"
Example
-------

.. code-block:: yaml
:caption: Setup example
events:
# React only on action-wise successful events
$/$/$/0:
handlers:
pipescript
pipescript:
program: /opt/bin/extra-logger.pl
quiet: false
format: json
18 changes: 6 additions & 12 deletions examples/models/router/events/events-config.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,16 @@ events:
$/$/$/0:
handler:
- console-logger
# - pipescript
console-logger:
concise: true
prefix: General flow

pipescript:
program: /path/to/your/bash-python-perl-script
quiet: false
format: json # or yaml

$/$/$/$:
handler:
- outcome-logger
Expand All @@ -22,15 +28,3 @@ events:
- console-logger
console-logger:
prefix: Asset Verification Error

#verify-process-running/syslogd/$/1:
# Handler name/id
# Handlers are part of sysinspect
# handler:
# - console-logger

# Optional configuration to the handler.
# Some handlers might accept specific config
# what to do with this data, once event came back.
# console-logger:
# prefix: Process Verification Error
4 changes: 4 additions & 0 deletions libsysinspect/src/reactor/handlers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod cstr_stdhdl;
pub mod evthandler;
pub mod pipescript;
pub mod stdhdl;

use lazy_static::lazy_static;

#[allow(clippy::type_complexity)]
Expand All @@ -16,6 +18,7 @@ pub mod registry {
use super::*;
use cstr_stdhdl::ConstraintHandler;
use evthandler::EventHandler;
use pipescript::PipeScriptHandler;
use std::{
collections::HashMap,
sync::{Mutex, MutexGuard},
Expand Down Expand Up @@ -47,6 +50,7 @@ pub mod registry {
log::debug!("Intialising handlers");
registry.insert(StdoutEventHandler::id(), |eid, cfg| Box::new(StdoutEventHandler::new(eid, cfg)));
registry.insert(ConstraintHandler::id(), |eid, cfg| Box::new(ConstraintHandler::new(eid, cfg)));
registry.insert(PipeScriptHandler::id(), |eid, cfg| Box::new(PipeScriptHandler::new(eid, cfg)));
}

/// Get all registered handlers.
Expand Down
101 changes: 101 additions & 0 deletions libsysinspect/src/reactor/handlers/pipescript.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
Pipescript is a handler that pipes the action output through a script
on certain event outcomes.
*/

use super::evthandler::EventHandler;
use crate::intp::{
actproc::response::ActionResponse,
conf::{EventConfig, EventConfigOption},
};
use colored::Colorize;
use core::str;
use serde_json::Value;
use std::{
io::Write,
process::{Command, Stdio},
};

#[derive(Default, Debug)]
pub struct PipeScriptHandler {
cfg: EventConfig,
}

impl PipeScriptHandler {
/// Format the output
fn fmt(&self, value: Option<Value>, format: &str) -> String {
let value = match value {
Some(v) => v,
None => return "".to_string(),
};

match format.to_lowercase().as_str() {
"yaml" => serde_yaml::to_string(&value).unwrap_or_default(),
_ => serde_json::to_string(&value).unwrap_or_default(),
}
}

/// Call user-defined script
fn call_script(&self, evt: &ActionResponse) {
// Successfull responses only
if evt.response.retcode() > 0 {
return;
}

// Config is required
let cfg = match self.config() {
Some(cfg) => cfg,
None => return,
};

// Program is required
let cmd = match cfg.as_string("program") {
Some(cmd) => cmd.split_whitespace().map(|s| s.to_string()).collect::<Vec<String>>(),
None => return,
};

// Verbosity
let quiet = cfg.as_bool("quiet").unwrap_or(false);

// Communication format
let format = cfg.as_string("format").unwrap_or("json".to_string());

match Command::new(&cmd[0]).args(&cmd[1..]).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()).spawn() {
Ok(mut p) => {
if let Some(mut stdin) = p.stdin.take() {
if let Err(err) = stdin.write_all(self.fmt(evt.response.data(), &format).as_bytes()) {
log::error!("Unable to pipe data to '{}': {}", cmd.join(" "), err);
} else if !quiet {
log::info!("{} - {}", "Pipescript".cyan(), cmd.join(" "));
}
}
}
Err(err) => log::error!("{} error: {} for '{}'", PipeScriptHandler::id(), err, cmd.join(" ")),
};
}
}

/// Pipescript handler
impl EventHandler for PipeScriptHandler {
fn new(_eid: String, cfg: crate::intp::conf::EventConfig) -> Self
where
Self: Sized,
{
PipeScriptHandler { cfg }
}

fn id() -> String
where
Self: Sized,
{
"pipescript".to_string()
}

fn handle(&self, evt: &ActionResponse) {
self.call_script(evt);
}

fn config(&self) -> Option<EventConfigOption> {
self.cfg.cfg(&PipeScriptHandler::id())
}
}

0 comments on commit 7ad17df

Please sign in to comment.