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}