falco_plugin/base/
mod.rs

1//! # The common foundation for all Falco plugins
2//!
3//! All plugins must implement the [`Plugin`] trait which specifies some basic metadata
4//! about the plugin.
5//!
6//! See the [`Plugin`] trait documentation for details.
7
8use crate::base::wrappers::BasePluginExported;
9use crate::tables::TablesInput;
10use schema::ConfigSchema;
11use std::ffi::CStr;
12
13mod logger;
14mod metrics;
15mod schema;
16#[doc(hidden)]
17pub mod wrappers;
18
19pub use metrics::{Metric, MetricLabel, MetricType, MetricValue};
20pub use schema::Json;
21
22/// The latest schema supported by the current SDK version
23pub use falco_plugin_api::SCHEMA_VERSION as CURRENT_SCHEMA_VERSION;
24
25/// # A base trait for implementing Falco plugins
26///
27/// There are several constants you need to set to describe the metadata for your plugin, described
28/// below. All the constants are C-style strings: you can initialize the fields with `c"foo"`.
29///
30/// For example, a plugin that doesn't support any capabilities (which is
31/// useless and would fail to load, but is a necessary step to building an actually useful plugin)
32/// might look like:
33///
34/// ```
35/// use std::ffi::CStr;
36/// use falco_plugin::base::{Metric, Plugin};
37/// use falco_plugin::plugin;
38/// use falco_plugin::FailureReason;
39/// use falco_plugin::tables::TablesInput;
40///
41/// // define the type holding the plugin state
42/// struct NoOpPlugin;
43///
44/// // implement the base::Plugin trait
45/// impl Plugin for NoOpPlugin {
46///     const NAME: &'static CStr = c"sample-plugin-rs";
47///     const PLUGIN_VERSION: &'static CStr = c"0.0.1";
48///     const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
49///     const CONTACT: &'static CStr = c"you@example.com";
50///     type ConfigType = ();
51///
52///     fn new(input: Option<&TablesInput>, config: Self::ConfigType)
53///         -> Result<Self, anyhow::Error> {
54///         Ok(NoOpPlugin)
55///     }
56/// }
57///
58/// // generate the actual plugin wrapper code
59/// // note we need to decorate the type name with `#[no_capabilities]` to bypass
60/// // the safeguard against building an invalid plugin
61/// plugin!(#[no_capabilities] NoOpPlugin);
62/// ```
63pub trait Plugin: BasePluginExported + Sized {
64    /// the name of your plugin, must match the plugin name in the Falco config file
65    const NAME: &'static CStr;
66    /// the version of your plugin
67    const PLUGIN_VERSION: &'static CStr;
68    /// a free-form description of what your plugin does
69    const DESCRIPTION: &'static CStr;
70    /// a way to contact you with issues regarding the plugin, be it email or a website
71    const CONTACT: &'static CStr;
72    /// the schema version supported by this plugin
73    ///
74    /// Usually should just be left as the default, but you may need to override it
75    /// if you're using a different version of the falco_event_schema crate.
76    const SCHEMA_VERSION: &'static CStr = CURRENT_SCHEMA_VERSION;
77
78    /// The plugin can be configured in three different ways. In all cases, an instance of the type
79    /// you specify will be passed to the [`Plugin::new`] method.
80    ///
81    /// See <https://falco.org/docs/plugins/usage/> for more information about plugin configuration
82    /// in Falco.
83    ///
84    /// ### No configuration
85    ///
86    /// If your plugin does not need any configuration, set the `ConfigType` to an empty tuple.
87    ///
88    /// ### Configuration as a string
89    ///
90    /// If you set the `ConfigType` to [`String`], your plugin will receive the configuration
91    /// as a string, read directly from the Falco config file.
92    ///
93    /// ### Configuration as JSON
94    ///
95    /// Plugins can also be configured using a JSON object. This will be parsed by the SDK and your
96    /// plugin will receive a data structure containing all the parsed fields. In order to use JSON
97    /// configuration, set the `ConfigType` to `Json<T>`, where the [`Json`](`crate::base::Json`)
98    /// type is provided by this crate and the type `T` must implement [`serde::de::DeserializeOwned`]
99    /// and [`schemars::JsonSchema`].
100    ///
101    /// You will also need to provide a JSON schema for the plugin API to validate the configuration.
102    ///
103    /// Please note that you can use the reexports (`falco_plugin::serde` and `falco_plugin::schemars`)
104    /// to ensure you're using the same version of serde and schemars as the SDK.
105    ///
106    /// Your config struct might look like:
107    ///
108    /// ```
109    /// use falco_plugin::schemars::JsonSchema;
110    /// use falco_plugin::serde::Deserialize;
111    ///
112    /// #[derive(JsonSchema, Deserialize)]
113    /// #[schemars(crate = "falco_plugin::schemars")]
114    /// #[serde(crate = "falco_plugin::serde")]
115    /// struct MyConfig {
116    ///     /* ... */
117    /// }
118    /// ```
119    ///
120    /// You can use irrefutable patterns in your `new` and `set_config` methods to make JSON configs
121    /// a little more ergonomic:
122    ///
123    /// ```
124    /// use std::ffi::CStr;
125    /// use anyhow::Error;
126    /// use falco_plugin::base::{Json, Metric, Plugin};
127    ///# use falco_plugin::plugin;
128    /// use falco_plugin::schemars::JsonSchema;
129    /// use falco_plugin::serde::Deserialize;
130    ///
131    /// use falco_plugin::tables::TablesInput;
132    ///
133    /// #[derive(JsonSchema, Deserialize)]
134    /// #[schemars(crate = "falco_plugin::schemars")]
135    /// #[serde(crate = "falco_plugin::serde")]
136    /// struct MyConfig {
137    ///     debug: bool,
138    /// }
139    ///
140    /// struct MyPlugin;
141    ///
142    /// impl Plugin for MyPlugin {
143    ///     // ...
144    ///#    const NAME: &'static CStr = c"";
145    ///#    const PLUGIN_VERSION: &'static CStr = c"";
146    ///#    const DESCRIPTION: &'static CStr = c"";
147    ///#    const CONTACT: &'static CStr = c"";
148    ///
149    ///     type ConfigType = Json<MyConfig>;
150    ///
151    ///     fn new(input: Option<&TablesInput>, Json(config): Json<MyConfig>) -> Result<Self, Error> {
152    ///     //                                  ^^^^^^^^^^^^
153    ///         if config.debug { /* ... */ }
154    ///
155    ///         // ...
156    ///#        todo!()
157    ///     }
158    ///
159    ///     fn set_config(&mut self, Json(config): Json<MyConfig>) -> Result<(), Error> {
160    ///     //                       ^^^^^^^^^^^^
161    ///         if config.debug { /* ... */ }
162    ///
163    ///         // ...
164    ///#        todo!()
165    ///     }
166    ///
167    ///#    fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
168    ///#        []
169    ///#    }
170    ///
171    ///     // ...
172    /// }
173    ///# plugin!(#[no_capabilities] MyPlugin);
174    /// ```
175    type ConfigType: ConfigSchema;
176
177    /// This method takes a [`TablesInput`](`crate::tables::TablesInput`) instance, which lets you
178    /// access tables exposed by other plugins (and Falco core).
179    ///
180    /// It should return a new instance of `Self`
181    fn new(input: Option<&TablesInput>, config: Self::ConfigType) -> Result<Self, anyhow::Error>;
182
183    /// Update the configuration of a running plugin
184    ///
185    /// The default implementation does nothing
186    fn set_config(&mut self, _config: Self::ConfigType) -> Result<(), anyhow::Error> {
187        Ok(())
188    }
189
190    /// Return the plugin metrics
191    ///
192    /// Metrics are described by:
193    /// - a name (just a string)
194    /// - a type (monotonic vs non-monotonic: [`crate::base::MetricType`])
195    /// - a value of one of the supported types ([`crate::base::MetricValue`])
196    ///
197    /// **Note**: The plugin name is prepended to the metric name, so a metric called `foo`
198    /// in a plugin called `bar` will be emitted by the plugin framework as `bar.foo`.
199    ///
200    /// **Note**: Metrics aren't registered in the framework in any way and there is no
201    /// requirement to report the same metrics on each call to `get_metrics`. However, it's
202    /// probably a good idea to do so, or at least not to change the type of metric or the type
203    /// of its value from call to call.
204    ///
205    /// There are two general patterns to use when emitting metrics from a plugin:
206    ///
207    /// 1. Predefined metrics
208    /// ```
209    ///# use std::ffi::CStr;
210    ///# use falco_plugin::base::{Metric, MetricLabel, MetricType, MetricValue, Plugin};
211    ///# use falco_plugin::plugin;
212    ///# use falco_plugin::FailureReason;
213    ///# use falco_plugin::tables::TablesInput;
214    ///#
215    ///# // define the type holding the plugin state
216    ///struct MyPlugin {
217    ///    // ...
218    ///    my_metric: MetricLabel,
219    ///}
220    ///#
221    ///# // implement the base::Plugin trait
222    ///# impl Plugin for MyPlugin {
223    ///#     const NAME: &'static CStr = c"sample-plugin-rs";
224    ///#     const PLUGIN_VERSION: &'static CStr = c"0.0.1";
225    ///#     const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
226    ///#     const CONTACT: &'static CStr = c"you@example.com";
227    ///#     type ConfigType = ();
228    ///#
229    ///     fn new(input: Option<&TablesInput>, config: Self::ConfigType)
230    ///         -> Result<Self, anyhow::Error> {
231    ///         Ok(MyPlugin {
232    ///             // ...
233    ///             my_metric: MetricLabel::new(c"my_metric", MetricType::Monotonic),
234    ///         })
235    ///     }
236    ///
237    ///#     fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
238    ///#         Ok(())
239    ///#     }
240    ///#
241    ///     fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
242    ///         [self.my_metric.with_value(MetricValue::U64(10u64))]
243    ///     }
244    ///# }
245    ///# plugin!(#[no_capabilities] MyPlugin);
246    /// ```
247    ///
248    /// 2. Inline metrics
249    /// ```
250    ///# use std::ffi::CStr;
251    ///# use falco_plugin::base::{Metric, MetricLabel, MetricType, MetricValue, Plugin};
252    ///# use falco_plugin::plugin;
253    ///# use falco_plugin::FailureReason;
254    ///# use falco_plugin::tables::TablesInput;
255    ///#
256    ///# // define the type holding the plugin state
257    ///# struct NoOpPlugin;
258    ///#
259    ///# // implement the base::Plugin trait
260    ///# impl Plugin for NoOpPlugin {
261    ///#     const NAME: &'static CStr = c"sample-plugin-rs";
262    ///#     const PLUGIN_VERSION: &'static CStr = c"0.0.1";
263    ///#     const DESCRIPTION: &'static CStr = c"A sample Falco plugin that does nothing";
264    ///#     const CONTACT: &'static CStr = c"you@example.com";
265    ///#     type ConfigType = ();
266    ///#
267    ///#     fn new(input: Option<&TablesInput>, config: Self::ConfigType)
268    ///#         -> Result<Self, anyhow::Error> {
269    ///#         Ok(NoOpPlugin)
270    ///#     }
271    ///#
272    ///#     fn set_config(&mut self, config: Self::ConfigType) -> Result<(), anyhow::Error> {
273    ///#         Ok(())
274    ///#     }
275    ///#
276    ///     fn get_metrics(&mut self) -> impl IntoIterator<Item=Metric> {
277    ///         [Metric::new(
278    ///             MetricLabel::new(c"my_metric", MetricType::Monotonic),
279    ///             MetricValue::U64(10u64),
280    ///         )]
281    ///     }
282    ///# }
283    ///# plugin!(#[no_capabilities] NoOpPlugin);
284    /// ```
285    fn get_metrics(&mut self) -> impl IntoIterator<Item = Metric> {
286        []
287    }
288}