Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Plugin status monitoring #25

Merged
merged 7 commits into from
Jul 23, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Working status parsing for bash plugins
subatiq committed Jul 10, 2024
commit fe9707904a31952924be8f256096667b4b37a332
17 changes: 11 additions & 6 deletions paws_config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::{collections::HashMap, path::PathBuf};
use serde::Deserialize;

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Duration(std::time::Duration);

impl Duration {
@@ -26,7 +26,7 @@ impl<'de> Deserialize<'de> for Duration {
}
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum StartupOptions {
Hot,
@@ -35,13 +35,13 @@ pub enum StartupOptions {
Delayed(Duration),
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
pub struct RandomRange<T> {
pub min: T,
pub max: T,
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
#[serde(rename_all = "snake_case")]
pub enum FrequencyOptions {
Once,
@@ -51,12 +51,17 @@ pub enum FrequencyOptions {
Random(RandomRange<Duration>),
}

#[derive(Debug, Deserialize)]
#[derive(Debug, Deserialize, Clone)]
pub struct MonitoringOptions {
pub frequency: FrequencyOptions,
}

#[derive(Debug, Deserialize, Clone)]
pub struct PluginConfig {
pub name: String,
pub startup: StartupOptions,
pub frequency: FrequencyOptions,
pub healthcheck: Option<String>,
pub monitoring: Option<MonitoringOptions>,
pub options: Option<HashMap<String, String>>
}

78 changes: 73 additions & 5 deletions src/plug.rs
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ use python_plugin::load as load_py_plugin;

use crate::intervals::{time_till_next_run, wait_duration};
use crate::stdout_styling::style_line;
use paws_config::{KittypawsConfig, PluginConfig, Duration as ConfigDuration};
use paws_config::{Duration as ConfigDuration, KittypawsConfig, PluginConfig};
use std::collections::HashMap;
use std::path::PathBuf;
use std::thread;
@@ -23,8 +23,17 @@ pub enum PluginLanguage {
Bash,
}

#[derive(Debug)]
pub enum StatusValue {
Int(i32),
Float(f32),
String(String),
}


pub trait PluginInterface {
fn run(&self, config: &HashMap<String, String>) -> Result<(), String>;
fn status(&self, config: &HashMap<String, String>) -> Result<HashMap<String, StatusValue>, String>;
}

#[derive(Debug)]
@@ -53,7 +62,23 @@ fn call_plugin(name: &str, plugin: &CallablePlugin, config: &HashMap<String, Str
}
}

fn start_plugin_loop(
fn get_status(name: &str, plugin: &CallablePlugin, config: &HashMap<String, String>) {
println!(
"{}",
style_line(name.to_string(), "Fetching status...".to_string())
);
match plugin.status(config) {
Ok(status) => {
println!(
"{}",
style_line(name.to_string(), format!("Status: {:?}", status))
);
}
Err(err) => panic!("Error while running plugin {}: {}", name, err),
}
}

fn start_execution_loop(
plugin: CallablePlugin,
config: PluginConfig,
loop_duration: &Option<ConfigDuration>,
@@ -64,7 +89,6 @@ fn start_plugin_loop(
if let Some(loop_duration) = loop_duration {
deadline = Some(Utc::now() + loop_duration.as_chrono());
}

thread::spawn(move || {
match startup {
StartupMode::Delayed(delay) => wait_duration(delay),
@@ -92,15 +116,59 @@ fn start_plugin_loop(
})
}

fn start_status_loop(
plugin: CallablePlugin,
config: PluginConfig,
loop_duration: &Option<ConfigDuration>,
) -> Option<JoinHandle<()>> {
if let Some(monitoring_config) = config.monitoring {
let mut deadline: Option<DateTime<Utc>> = None;

if let Some(loop_duration) = loop_duration {
deadline = Some(Utc::now() + loop_duration.as_chrono());
}

return Some(thread::spawn(move || loop {
let status = get_status(
&config.name,
&plugin,
&config.options.clone().unwrap_or_default(),
);
println!("Status {:?}", status);
if time_till_next_run(&monitoring_config.frequency).is_none() {
break;
}
if let Some(deadline) = deadline {
if Utc::now() > deadline {
break;
}
}
}));
}

None
}

pub fn start_main_loop(config: KittypawsConfig) {
let mut handles: Vec<JoinHandle<()>> = Vec::new();

for plugconf in config.plugins {
// TODO: Stop this uglyness
match load_plugin(&plugconf.name) {
Ok(plugin) => {
if let Some(status_thread) =
start_status_loop(plugin, plugconf.clone(), &config.duration)
{
handles.push(status_thread);
}
}
Err(err) => println!("! WARNING: {}", err),
}
match load_plugin(&plugconf.name) {
Ok(plugin) => {
let thread = start_plugin_loop(plugin, plugconf, &config.duration);
let exec_thread = start_execution_loop(plugin, plugconf, &config.duration);

handles.push(thread);
handles.push(exec_thread);
}
Err(err) => println!("! WARNING: {}", err),
}
52 changes: 49 additions & 3 deletions src/plug/bash_plugin.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
use std::io::BufRead;
use std::process::Command;
use std::path::{Path,PathBuf};
use std::collections::HashMap;
use crate::plug::{unwrap_home_path, PluginInterface, CallablePlugin, PLUGINS_PATH};

use super::StatusValue;


struct BashCommand {
executable: PathBuf,
status_checker: Option<PathBuf>,
}


@@ -23,6 +27,38 @@ impl PluginInterface for BashCommand {
println!("{}", String::from_utf8(output.stdout).unwrap());
Ok(())
}

fn status(&self, config: &HashMap<String, String>) -> Result<HashMap<String, StatusValue>, String> {
// get a string of args and values from hashmap
if let Some(command) = &self.status_checker {
let output = Command::new("bash")
.envs(config)
.arg("-C")
.arg(command.to_str().unwrap())
// .args(&args)
.output()
.expect("failed to execute process {}");

let mut status = HashMap::new();
for key_value in output.stdout.lines() {
if let Ok(key_value) = key_value {
if let Some((key, value)) = key_value.split_once("=") {
let mut parsed_value = StatusValue::String(value.to_string());
if let Ok(value) = value.parse::<i32>() {
parsed_value = StatusValue::Int(value);
}
else if let Ok(value) = value.parse::<f32>() {
parsed_value = StatusValue::Float(value);
}
status.insert(key.to_string(), parsed_value);
}
}
}
return Ok(status);
}

unreachable!()
}
}

pub fn load(name: &str) -> Result<CallablePlugin, String> {
@@ -32,9 +68,19 @@ pub fn load(name: &str) -> Result<CallablePlugin, String> {
let entrypoint_path = format!("{}/{}/run.sh", &plugins_dirname, name);
let path_to_main = Path::new(&entrypoint_path);

match path_to_main.exists() {
true => Ok(Box::new(BashCommand { executable : path_to_main.to_path_buf() })),
false => Err(format!("No main.py found for plugin: {}", name))
let entrypoint_path = format!("{}/{}/status.sh", &plugins_dirname, name);
let path_to_status = Path::new(&entrypoint_path);
let executable = path_to_main.to_path_buf();

let mut status_checker = None;
if path_to_status.exists() {
status_checker = Some(path_to_status.to_path_buf());
}

if !path_to_main.exists() {
return Err(format!("No main.py found for plugin: {}", name));
}

Ok(Box::new(BashCommand { executable, status_checker }))
}

6 changes: 6 additions & 0 deletions src/plug/python_plugin.rs
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@ use std::collections::HashMap;
use std::fs;
use std::path::Path;

use super::StatusValue;

impl PluginInterface for pyo3::Py<PyAny> {
fn run(&self, config: &HashMap<String, String>) -> Result<(), String> {
let mut pyconfig = HashMap::new();
@@ -18,6 +20,10 @@ impl PluginInterface for pyo3::Py<PyAny> {

Ok(())
}

fn status(&self, _: &HashMap<String, String>) -> Result<HashMap<String, StatusValue>, String> {
unimplemented!("Python plugins do not support status checks now")
}
}

pub fn load(name: &str) -> Result<CallablePlugin, String> {