Files
keep/src/meta_plugin/mod.rs
Andrew Phillips 8379ae2136 refactor: rename plugin features with type prefix for consistency
- Plugin features now use type_ prefix (meta_magic, filter_grep, etc.)
- Added meta_all_musl and filter_all_musl for MUSL-compatible builds
- grep filter plugin made optional via filter_grep feature flag
- Removed regex crate from grep-related code, uses strip_prefix instead
- Updated CHANGELOG.md with breaking change documentation
2026-03-21 17:36:29 -03:00

656 lines
19 KiB
Rust

use log::{debug, warn};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub mod cwd;
pub mod digest;
pub mod env;
pub mod exec;
pub mod hostname;
#[cfg(feature = "meta_infer")]
pub mod infer_plugin;
pub mod keep_pid;
pub mod magic_file;
pub mod read_rate;
pub mod read_time;
pub mod shell;
pub mod shell_pid;
pub mod text;
#[cfg(feature = "meta_tokens")]
pub mod tokens;
#[cfg(feature = "meta_tree_magic_mini")]
pub mod tree_magic_mini;
pub mod user;
pub use digest::DigestMetaPlugin;
pub use exec::MetaPluginExec;
#[cfg(feature = "meta_magic")]
pub use magic_file::MagicFileMetaPlugin;
// pub use text::TextMetaPlugin; // Removed duplicate
pub use cwd::CwdMetaPlugin;
pub use env::EnvMetaPlugin;
pub use hostname::HostnameMetaPlugin;
#[cfg(feature = "meta_infer")]
pub use infer_plugin::InferMetaPlugin;
pub use keep_pid::KeepPidMetaPlugin;
pub use read_rate::ReadRateMetaPlugin;
pub use read_time::ReadTimeMetaPlugin;
pub use shell::ShellMetaPlugin;
pub use shell_pid::ShellPidMetaPlugin;
#[cfg(feature = "meta_tree_magic_mini")]
pub use tree_magic_mini::TreeMagicMiniMetaPlugin;
pub use user::UserMetaPlugin;
#[cfg(not(feature = "meta_magic"))]
pub use magic_file::FallbackMagicFileMetaPlugin as MagicFileMetaPlugin;
type PluginConstructor = fn(
Option<HashMap<String, serde_yaml::Value>>,
Option<HashMap<String, serde_yaml::Value>>,
) -> Box<dyn MetaPlugin>;
/// Represents metadata to be stored.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetaData {
/// The name of the metadata field.
pub name: String,
/// The value of the metadata field.
pub value: String,
}
/// Response from meta plugin operations.
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct MetaPluginResponse {
/// The generated metadata items.
pub metadata: Vec<MetaData>,
/// Indicates if the plugin has finished processing.
pub is_finalized: bool,
}
/// Type alias for the save_meta callback shared by all plugins.
pub type SaveMetaFn = Arc<Mutex<dyn FnMut(&str, &str) + Send>>;
/// Creates a no-op save_meta for plugins not wired through MetaService.
pub fn noop_save_meta() -> SaveMetaFn {
Arc::new(Mutex::new(|_: &str, _: &str| {}))
}
/// Base implementation for meta plugins to reduce boilerplate.
#[derive(Clone)]
pub struct BaseMetaPlugin {
/// Output mappings for metadata.
pub outputs: std::collections::HashMap<String, serde_yaml::Value>,
/// Configuration options for the plugin.
pub options: std::collections::HashMap<String, serde_yaml::Value>,
/// Whether the plugin is finalized.
pub is_finalized: bool,
/// Callback to store metadata. Called directly by plugins.
pub save_meta: SaveMetaFn,
}
impl std::fmt::Debug for BaseMetaPlugin {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("BaseMetaPlugin")
.field("outputs", &self.outputs)
.field("options", &self.options)
.field("is_finalized", &self.is_finalized)
.finish_non_exhaustive()
}
}
impl Default for BaseMetaPlugin {
fn default() -> Self {
Self {
outputs: HashMap::new(),
options: HashMap::new(),
is_finalized: false,
save_meta: noop_save_meta(),
}
}
}
impl BaseMetaPlugin {
/// Creates a new `BaseMetaPlugin`.
///
/// # Returns
///
/// A new instance of `BaseMetaPlugin`.
pub fn new() -> Self {
Self::default()
}
/// Returns a reference to the outputs mapping.
pub fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
&self.outputs
}
/// Returns a mutable reference to the outputs mapping.
pub fn outputs_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
&mut self.outputs
}
/// Returns a reference to the options mapping.
pub fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
&self.options
}
/// Returns a mutable reference to the options mapping.
pub fn options_mut(&mut self) -> &mut std::collections::HashMap<String, serde_yaml::Value> {
&mut self.options
}
/// Sets the save_meta callback on the base plugin.
pub fn set_save_meta(&mut self, save_meta: SaveMetaFn) {
self.save_meta = save_meta;
}
/// Saves a metadata entry via the save_meta callback.
pub fn save_meta(&self, name: &str, value: &str) {
if let Ok(mut f) = self.save_meta.lock() {
f(name, value);
} else {
warn!("META_PLUGIN: save_meta lock poisoned, dropping metadata: {name}={value}");
}
}
/// Helper function to initialize plugin options and outputs.
///
/// # Arguments
///
/// * `default_outputs` - Slice of default output names.
/// * `options` - Optional user-provided options.
/// * `outputs` - Optional user-provided outputs.
pub fn initialize_plugin(
&mut self,
default_outputs: &[&str],
options: &Option<std::collections::HashMap<String, serde_yaml::Value>>,
outputs: &Option<std::collections::HashMap<String, serde_yaml::Value>>,
) {
// Set default outputs
for output_name in default_outputs {
self.outputs.insert(
output_name.to_string(),
serde_yaml::Value::String(output_name.to_string()),
);
}
// Apply provided options and outputs
if let Some(opts) = options {
for (key, value) in opts {
self.options.insert(key.clone(), value.clone());
}
}
if let Some(outs) = outputs {
for (key, value) in outs {
self.outputs.insert(key.clone(), value.clone());
}
}
}
}
impl MetaPlugin for BaseMetaPlugin {
/// Returns the type of this meta plugin.
///
/// # Returns
///
/// `MetaPluginType::Text` (default for base).
fn meta_type(&self) -> MetaPluginType {
// This is a base implementation, so we need to return something
// This might not be used, but we need to satisfy the trait
MetaPluginType::Text
}
/// Returns a reference to the outputs mapping.
///
/// # Returns
///
/// A reference to the `HashMap` of outputs.
fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
&self.outputs
}
/// Returns a mutable reference to the outputs mapping.
///
/// # Returns
///
/// A mutable reference to the `HashMap` of outputs.
fn outputs_mut(
&mut self,
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
Ok(&mut self.outputs)
}
/// Returns a reference to the options mapping.
///
/// # Returns
///
/// A reference to the `HashMap` of options.
fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
&self.options
}
/// Returns a mutable reference to the options mapping.
///
/// # Returns
///
/// A mutable reference to the `HashMap` of options.
fn options_mut(
&mut self,
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
Ok(&mut self.options)
}
}
#[derive(
Debug,
Eq,
PartialEq,
Clone,
Hash,
strum::EnumIter,
strum::Display,
strum::EnumString,
Serialize,
Deserialize,
)]
#[strum(serialize_all = "snake_case", ascii_case_insensitive)]
pub enum MetaPluginType {
MagicFile,
Cwd,
Text,
User,
Shell,
ShellPid,
KeepPid,
Digest,
ReadTime,
ReadRate,
Hostname,
Exec,
Env,
Tokens,
TreeMagicMini,
Infer,
}
/// Central function to handle metadata output with name mapping.
///
/// # Arguments
///
/// * `internal_name` - The internal name of the metadata.
/// * `value` - The value to process.
/// * `outputs` - The outputs mapping.
///
/// # Returns
///
/// An optional `MetaData` if the output is enabled, `None` if disabled.
pub fn process_metadata_outputs(
internal_name: &str,
value: serde_yaml::Value,
outputs: &std::collections::HashMap<String, serde_yaml::Value>,
) -> Option<MetaData> {
// Check if this output is disabled
if let Some(mapping) = outputs.get(internal_name) {
// Check for null to disable the output
if mapping.is_null() {
debug!("META: Skipping disabled output (null): {internal_name}");
return None;
}
// Check for boolean false to disable the output
if let Some(false_val) = mapping.as_bool()
&& !false_val
{
debug!("META: Skipping disabled output: {internal_name}");
return None;
}
if let Some(custom_name) = mapping.as_str() {
let value_str = yaml_value_to_string(&value);
debug!(
"META: Processing metadata: internal_name={internal_name}, custom_name={custom_name}, value={value_str}"
);
return Some(MetaData {
name: custom_name.to_string(),
value: value_str,
});
}
}
let value_str = yaml_value_to_string(&value);
// Default: use internal name as output name
debug!("META: Processing metadata: name={internal_name}, value={value_str}");
Some(MetaData {
name: internal_name.to_string(),
value: value_str,
})
}
fn yaml_value_to_string(value: &serde_yaml::Value) -> String {
match value {
serde_yaml::Value::Null => "null".to_string(),
serde_yaml::Value::Bool(b) => b.to_string(),
serde_yaml::Value::Number(n) => n.to_string(),
serde_yaml::Value::String(s) => s.clone(),
serde_yaml::Value::Sequence(_)
| serde_yaml::Value::Mapping(_)
| serde_yaml::Value::Tagged(_) => {
serde_yaml::to_string(value).unwrap_or_else(|_| "".to_string())
}
}
}
pub trait MetaPlugin: Send
where
Self: 'static,
{
/// Returns the type of this meta plugin.
///
/// # Returns
///
/// The `MetaPluginType` enum variant for this plugin.
fn meta_type(&self) -> MetaPluginType;
/// Checks if the plugin is supported on the current system.
///
/// # Returns
///
/// `true` if supported, `false` otherwise.
fn is_supported(&self) -> bool {
true
}
/// Checks if the plugin is internal (built-in).
///
/// # Returns
///
/// `true` if internal, `false` otherwise.
fn is_internal(&self) -> bool {
true
}
/// Checks if the plugin is already finalized.
///
/// # Returns
///
/// `true` if finalized, `false` otherwise.
fn is_finalized(&self) -> bool {
false
}
/// Sets the finalized state (only for plugins that can track this).
///
/// # Arguments
///
/// * `_finalized` - The new finalized state (unused in default).
fn set_finalized(&mut self, _finalized: bool) {}
/// Updates the meta plugin with new data.
///
/// # Arguments
///
/// * `_data` - The data chunk to process (unused in default).
///
/// # Returns
///
/// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `false`.
fn update(&mut self, _data: &[u8]) -> MetaPluginResponse {
// Default implementation does nothing
MetaPluginResponse {
metadata: Vec::new(),
is_finalized: false,
}
}
/// Finalizes the plugin.
///
/// # Returns
///
/// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `true`.
fn finalize(&mut self) -> MetaPluginResponse {
// Default implementation does nothing
MetaPluginResponse {
metadata: Vec::new(),
is_finalized: true,
}
}
/// Gets program information for display in status.
///
/// # Returns
///
/// An optional tuple of program name and arguments, or `None`.
fn program_info(&self) -> Option<(&str, Vec<&str>)> {
None
}
/// Initializes the plugin.
///
/// # Returns
///
/// A `MetaPluginResponse` with empty metadata and `is_finalized` set to `false`.
fn initialize(&mut self) -> MetaPluginResponse {
// Default implementation does nothing
MetaPluginResponse {
metadata: Vec::new(),
is_finalized: false,
}
}
/// Returns a reference to the outputs mapping.
///
/// # Returns
///
/// An empty `HashMap` (default implementation).
fn outputs(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
use std::sync::LazyLock;
static EMPTY: LazyLock<std::collections::HashMap<String, serde_yaml::Value>> =
LazyLock::new(std::collections::HashMap::new);
&EMPTY
}
/// Returns a mutable reference to the outputs mapping.
///
/// # Returns
///
/// A mutable reference to the outputs `HashMap`.
///
/// # Errors
///
/// Returns an error if the plugin does not support mutable outputs.
fn outputs_mut(
&mut self,
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
anyhow::bail!("outputs_mut() not supported by this plugin")
}
/// Returns a reference to the options mapping.
///
/// # Returns
///
/// An empty `HashMap` (default implementation).
fn options(&self) -> &std::collections::HashMap<String, serde_yaml::Value> {
use std::sync::LazyLock;
static EMPTY: LazyLock<std::collections::HashMap<String, serde_yaml::Value>> =
LazyLock::new(std::collections::HashMap::new);
&EMPTY
}
/// Returns a mutable reference to the options mapping.
///
/// # Returns
///
/// A mutable reference to the options `HashMap`.
///
/// # Errors
///
/// Returns an error if the plugin does not support mutable options.
fn options_mut(
&mut self,
) -> anyhow::Result<&mut std::collections::HashMap<String, serde_yaml::Value>> {
anyhow::bail!("options_mut() not supported by this plugin")
}
/// Gets the default output names this plugin can produce.
///
/// # Returns
///
/// A vector containing the meta type as a string (default).
fn default_outputs(&self) -> Vec<String> {
// Default implementation returns the meta type as a string
vec![self.meta_type().to_string()]
}
/// Returns a description of this plugin for display in config templates.
///
/// # Returns
///
/// A description string (empty by default).
fn description(&self) -> &str {
""
}
/// Returns true if this plugin can execute concurrently with other
/// parallel-safe plugins.
///
/// Plugins that do significant per-chunk work (hashing, tokenization,
/// piping to child processes) should return true. The MetaService will
/// run all parallel-safe plugins in separate threads per phase, then
/// process results sequentially.
fn parallel_safe(&self) -> bool {
false
}
/// Builds the schema for this plugin from its options and outputs.
///
/// Default implementation infers option types from YAML values and
/// collects enabled outputs.
///
/// # Returns
///
/// A `PluginSchema` describing this plugin's configuration.
fn schema(&self) -> crate::common::schema::PluginSchema {
use crate::common::schema::{OptionSchema, OptionType, OutputSchema, PluginSchema};
let options: Vec<OptionSchema> = self
.options()
.iter()
.map(|(key, value)| {
let option_type = OptionType::from_yaml_value(value);
let (default, required) = if value.is_null() {
(None, true)
} else {
(Some(value.clone()), false)
};
OptionSchema {
name: key.clone(),
option_type,
default,
required,
}
})
.collect();
let mut outputs: Vec<OutputSchema> = Vec::new();
for (key, value) in self.outputs() {
if !value.is_null() {
outputs.push(OutputSchema {
name: key.clone(),
description: key.clone(),
});
}
}
if outputs.is_empty() {
for output_name in self.default_outputs() {
outputs.push(OutputSchema {
name: output_name.clone(),
description: output_name,
});
}
}
PluginSchema {
name: self.meta_type().to_string(),
description: self.description().to_string(),
options,
outputs,
}
}
/// Method to downcast to concrete type (for checking finalization state).
///
/// # Returns
///
/// A mutable reference to `self` as `dyn Any`.
fn as_any_mut(&mut self) -> &mut dyn std::any::Any
where
Self: Sized,
{
self
}
/// Sets the save_meta callback for this plugin.
///
/// Called by MetaService to wire the plugin to the metadata storage.
fn set_save_meta(&mut self, _save_meta: SaveMetaFn) {}
/// Saves a metadata entry via the save_meta callback.
///
/// Plugins call this during initialize/update/finalize to persist metadata.
fn save_meta(&self, _name: &str, _value: &str) {}
}
/// Global registry for meta plugins.
static META_PLUGIN_REGISTRY: std::sync::LazyLock<
Mutex<HashMap<MetaPluginType, PluginConstructor>>,
> = std::sync::LazyLock::new(|| Mutex::new(HashMap::new()));
/// Register a meta plugin with the global registry.
///
/// # Arguments
///
/// * `meta_plugin_type` - The type of the meta plugin to register.
/// * `constructor` - The constructor function for creating plugin instances.
pub fn register_meta_plugin(
meta_plugin_type: MetaPluginType,
constructor: PluginConstructor,
) -> anyhow::Result<()> {
META_PLUGIN_REGISTRY
.lock()
.map_err(|e| anyhow::anyhow!("plugin registry poisoned: {e}"))?
.insert(meta_plugin_type, constructor);
Ok(())
}
pub fn get_meta_plugin(
meta_plugin_type: MetaPluginType,
options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
) -> anyhow::Result<Box<dyn MetaPlugin>> {
get_meta_plugin_with_save(meta_plugin_type, options, outputs, None)
}
/// Creates a meta plugin instance with an optional save_meta callback.
///
/// If `save_meta` is provided, it is wired to the plugin so it can
/// store metadata directly during initialize/update/finalize.
pub fn get_meta_plugin_with_save(
meta_plugin_type: MetaPluginType,
options: Option<std::collections::HashMap<String, serde_yaml::Value>>,
outputs: Option<std::collections::HashMap<String, serde_yaml::Value>>,
save_meta: Option<SaveMetaFn>,
) -> anyhow::Result<Box<dyn MetaPlugin>> {
let registry = META_PLUGIN_REGISTRY
.lock()
.map_err(|e| anyhow::anyhow!("plugin registry poisoned: {e}"))?;
if let Some(constructor) = registry.get(&meta_plugin_type) {
let mut plugin = constructor(options, outputs);
if let Some(sm) = save_meta {
plugin.set_save_meta(sm);
}
return Ok(plugin);
}
anyhow::bail!("Meta plugin {meta_plugin_type:?} not registered")
}