Proof of concept for Plugin system (fixes #3562)

This commit is contained in:
Felix Ableitner 2024-05-03 12:05:25 +02:00
parent 7540b02723
commit a65ccaf665
12 changed files with 4439 additions and 2295 deletions

3
.gitignore vendored
View file

@ -34,3 +34,6 @@ dev_pgdata/
# database dumps # database dumps
*.sqldump *.sqldump
# compiled example plugin
example_plugin/plugin.wasm

1354
Cargo.lock generated

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -49,8 +49,6 @@ else
done done
fi fi
echo "$PWD"
LOG_DIR=target/log LOG_DIR=target/log
mkdir -p $LOG_DIR mkdir -p $LOG_DIR
@ -75,6 +73,11 @@ LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_delta.hjson \
LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \ LEMMY_DATABASE_URL="${LEMMY_DATABASE_URL}/lemmy_delta" \
target/lemmy_server >$LOG_DIR/lemmy_delta.out 2>&1 & target/lemmy_server >$LOG_DIR/lemmy_delta.out 2>&1 &
# plugin setup
pushd example_plugin
tinygo build -o plugin.wasm -target wasi main.go
popd
echo "start epsilon" echo "start epsilon"
# An instance who has a blocklist, with lemmy-alpha blocked # An instance who has a blocklist, with lemmy-alpha blocked
LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \ LEMMY_CONFIG_LOCATION=./docker/federation/lemmy_epsilon.hjson \

View file

@ -11,7 +11,7 @@ killall -s1 lemmy_server || true
popd popd
pnpm i pnpm i
pnpm api-test || true pnpm api-test-post || true
killall -s1 lemmy_server || true killall -s1 lemmy_server || true
killall -s1 pict-rs || true killall -s1 pict-rs || true

View file

@ -763,3 +763,14 @@ test("Fetch post with redirect", async () => {
let gammaPost2 = await gamma.resolveObject(form); let gammaPost2 = await gamma.resolveObject(form);
expect(gammaPost2.post).toBeDefined(); expect(gammaPost2.post).toBeDefined();
}); });
test("Create a post", async () => {
let community = await createCommunity(epsilon);
let postRes = createPost(
epsilon,
community.community_view.community.id,
"https://example.com/",
"plugin should block this",
);
await expect(postRes).rejects.toStrictEqual(Error("plugin_error"));
});

View file

@ -28,8 +28,11 @@ uuid = { workspace = true }
moka.workspace = true moka.workspace = true
once_cell.workspace = true once_cell.workspace = true
anyhow.workspace = true anyhow.workspace = true
serde.workspace = true
webmention = "0.5.0" webmention = "0.5.0"
accept-language = "3.1.0" accept-language = "3.1.0"
extism = "1.2.0"
extism-convert = "1.2.0"
[package.metadata.cargo-machete] [package.metadata.cargo-machete]
ignored = ["futures"] ignored = ["futures"]

View file

@ -1,5 +1,6 @@
use activitypub_federation::config::Data; use activitypub_federation::config::Data;
use actix_web::web::Json; use actix_web::web::Json;
use extism::*;
use lemmy_api_common::{ use lemmy_api_common::{
build_response::build_post_response, build_response::build_post_response,
context::LemmyContext, context::LemmyContext,
@ -46,6 +47,8 @@ use lemmy_utils::{
}, },
}, },
}; };
use serde::Serialize;
use std::{ffi::OsStr, fs::read_dir};
use tracing::Instrument; use tracing::Instrument;
use url::Url; use url::Url;
use webmention::{Webmention, WebmentionError}; use webmention::{Webmention, WebmentionError};
@ -123,6 +126,8 @@ pub async fn create_post(
} }
}; };
plugin_hook("api_create_post", data.clone())?;
let post_form = PostInsertForm::builder() let post_form = PostInsertForm::builder()
.name(data.name.trim().to_string()) .name(data.name.trim().to_string())
.url(url) .url(url)
@ -202,3 +207,31 @@ pub async fn create_post(
build_post_response(&context, community_id, &local_user_view.person, post_id).await build_post_response(&context, community_id, &local_user_view.person, post_id).await
} }
fn load_plugins() -> LemmyResult<Plugin> {
// TODO: make dir configurable via env var
let plugin_paths = read_dir("example_plugin")?;
let mut wasm_files = vec![];
for path in plugin_paths {
let path = path?.path();
if path.extension() == Some(OsStr::new("wasm")) {
wasm_files.push(path);
}
}
let manifest = Manifest::new(wasm_files);
let plugin = Plugin::new(&manifest, [], true)?;
Ok(plugin)
}
fn plugin_hook<T: Serialize>(name: &'static str, data: T) -> LemmyResult<()> {
let mut plugin = load_plugins()?;
if plugin.function_exists(name) {
let res = plugin
.call::<extism_convert::Json<T>, &str>(name, data.into())
.map_err(|e| LemmyErrorType::PluginError(e.to_string()));
dbg!(&res);
println!("{}", res?);
}
Ok(())
}

View file

@ -177,6 +177,7 @@ pub enum LemmyErrorType {
InvalidBotAction, InvalidBotAction,
CantBlockLocalInstance, CantBlockLocalInstance,
UrlWithoutDomain, UrlWithoutDomain,
PluginError(String),
Unknown(String), Unknown(String),
} }

5
example_plugin/go.mod Normal file
View file

@ -0,0 +1,5 @@
module example_plugin
go 1.22.0
require github.com/extism/go-pdk v1.0.2 // indirect

2
example_plugin/go.sum Normal file
View file

@ -0,0 +1,2 @@
github.com/extism/go-pdk v1.0.2 h1:UB7oTW3tw2zoMlsUdBEDAAbhQg9OudzgNeyCwQYZ730=
github.com/extism/go-pdk v1.0.2/go.mod h1:Gz+LIU/YCKnKXhgge8yo5Yu1F/lbv7KtKFkiCSzW/P4=

32
example_plugin/main.go Normal file
View file

@ -0,0 +1,32 @@
package main
import (
"github.com/extism/go-pdk"
"errors"
)
type CreatePost struct {
Name string `json:"name"`
Body string `json:"body"`
// skipping other fields for now
}
//export api_create_post
func api_create_post() int32 {
params := CreatePost{}
// use json input helper, which automatically unmarshals the plugin input into your struct
err := pdk.InputJSON(&params)
if err != nil {
pdk.SetError(err)
return 1
}
if params.Body == "plugin should block this" {
pdk.SetError(errors.New("blocked by plugin"))
return 1
}
greeting := `Created post "` + params.Name + `"!`
pdk.OutputString(greeting)
return 0
}
func main() {}