Full plugin example
Every hook type in one plugin.
Complete v7 plugin with config, request, route, JSON, and CloudPanel hooks. Production source: plugins/hello ↗
use featherfly_plugin_sdk::{
declare_plugin, hook, hook_cloudpanel, hook_config, hook_json, hook_request, log_info, route,
write_cloudpanel_cancel, write_json_output, write_route_response, write_yaml_output,
CloudPanelCommandContext, ConfigMutateContext, EventContext, HookResult, HostApi,
JsonMutateContext, JsonMutateTarget, PluginEvent, RequestHookContext, RequestHookPhase,
RouteHandlerContext, CONFIG_MUTATE_UNCHANGED, CLOUDPANEL_CONTINUE, HTTP_METHOD_GET,
JSON_MUTATE_UNCHANGED, REQUEST_CONTINUE,
};
extern "C" fn init(host: *const HostApi) -> i32 {
hook!(host, PluginEvent::DaemonStarted, on_started);
hook_config!(host, on_config);
hook_request!(host, RequestHookPhase::Intercept, "/api/*", on_intercept);
hook_json!(host, JsonMutateTarget::ResponseBody, "/api/system", on_body);
hook_cloudpanel!(host, on_cloudpanel);
route!(host, HTTP_METHOD_GET, "/plugins/hello", on_route);
unsafe { log_info(host, "ready") };
0
}
extern "C" fn on_started(_ctx: *const EventContext) -> HookResult {
HookResult::r#continue()
}
extern "C" fn on_config(ctx: *const ConfigMutateContext) -> i32 {
let ctx = unsafe { &*ctx };
let input = unsafe { std::slice::from_raw_parts(ctx.yaml_in_ptr, ctx.yaml_in_len) };
if input.starts_with(b"# my-plugin\n") {
return CONFIG_MUTATE_UNCHANGED;
}
let mut out = b"# my-plugin\n".to_vec();
out.extend_from_slice(input);
write_yaml_output(ctx, &out)
}
extern "C" fn on_intercept(_ctx: *const RequestHookContext) -> i32 {
REQUEST_CONTINUE
}
extern "C" fn on_body(ctx: *const JsonMutateContext) -> i32 {
let ctx = unsafe { &*ctx };
let input = unsafe { std::slice::from_raw_parts(ctx.json_in_ptr, ctx.json_in_len) };
let Ok(mut value) = serde_json::from_slice::<serde_json::Value>(input) else {
return JSON_MUTATE_UNCHANGED;
};
if let Some(map) = value.as_object_mut() {
map.insert("my_plugin".into(), true.into());
}
let Ok(out) = serde_json::to_vec(&value) else { return JSON_MUTATE_UNCHANGED };
write_json_output(ctx, &out)
}
extern "C" fn on_cloudpanel(ctx: *const CloudPanelCommandContext) -> i32 {
let ctx = unsafe { &*ctx };
let command = unsafe { std::slice::from_raw_parts(ctx.command_ptr, ctx.command_len) };
if command == b"site:delete" {
return write_cloudpanel_cancel(ctx, b"site deletion disabled by my-plugin");
}
CLOUDPANEL_CONTINUE
}
extern "C" fn on_route(ctx: *const RouteHandlerContext) -> i32 {
let ctx = unsafe { &*ctx };
write_route_response(ctx, 200, br#"{"ok":true}"#)
}
declare_plugin! { name: "my-plugin", version: "0.1.0", init: init }