falco_plugin/plugin/base/
mod.rs

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