Skip to content

New plugin system #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 47 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
12d7b8c
WIP: intermediate changes
ludomikula Aug 7, 2023
d3cf4ba
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
a119225
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
f42b4f5
new: make default max query timeout configurable
ludomikula Aug 20, 2023
005611d
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
f3358b5
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
1b820ca
Add support for SUPER_ADMIN role
aq-ikhwa-tech Sep 7, 2023
ec518d8
Publish server log event for ee plugin to consume
aq-ikhwa-tech Sep 28, 2023
8b30ced
new: reworked Plugin classloader system
ludomikula Oct 10, 2023
0b54e90
WIP: intermediate changes
ludomikula Aug 7, 2023
47d1dad
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
0451776
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
042f511
new: make default max query timeout configurable
ludomikula Aug 20, 2023
0cfad7d
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
d8ed9c1
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
dda057d
Publish server log event for ee plugin to consume
aq-ikhwa-tech Sep 28, 2023
8d0c483
new: reworked Plugin classloader system
ludomikula Oct 10, 2023
e5afe8f
Add handling for audit logs feature
aq-ikhwa-tech Nov 4, 2023
a8cc6d9
Add handling for geolocation data for audit logs
aq-ikhwa-tech Nov 6, 2023
39e3e1f
new: extend new plugin system
ludomikula Nov 6, 2023
4bd4373
WIP: intermediate changes
ludomikula Aug 7, 2023
6e57773
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
1dff91e
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
04498f9
new: make default max query timeout configurable
ludomikula Aug 20, 2023
77ea427
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
c60b4da
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
46179cb
Publish server log event for ee plugin to consume
aq-ikhwa-tech Sep 28, 2023
f81924f
new: reworked Plugin classloader system
ludomikula Oct 10, 2023
e582b1e
WIP: intermediate changes
ludomikula Aug 7, 2023
d394e76
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
4429800
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
3194009
new: make default max query timeout configurable
ludomikula Aug 20, 2023
25dad14
WIP: new plugin system first iteration
ludomikula Aug 12, 2023
67d6a74
fix: check plugin endpoint method for proper return class
ludomikula Aug 28, 2023
d1224a1
Publish server log event for ee plugin to consume
aq-ikhwa-tech Sep 28, 2023
4ae0e0a
new: reworked Plugin classloader system
ludomikula Oct 10, 2023
4930575
Add handling for audit logs feature
aq-ikhwa-tech Nov 4, 2023
63d5dde
Add handling for geolocation data for audit logs
aq-ikhwa-tech Nov 6, 2023
7a3b3e5
new: extend new plugin system
ludomikula Nov 6, 2023
ea9d6fc
Add handling for api delays in case of rate limit
aq-ikhwa-tech Nov 8, 2023
083110c
fix: reload router functions when a plugin adds endpoints
ludomikula Nov 9, 2023
c5f6d5d
wip: plugin endpoint authentication
ludomikula Nov 13, 2023
19b0c4d
new: propagate plugin specific environment variables to plugins
ludomikula Nov 19, 2023
f145904
new: add environment variable for controlling plugin location
ludomikula Nov 19, 2023
cdc42c6
new: implemented plugin endpoints security
ludomikula Feb 2, 2024
acc402f
Merge remote-tracking branch 'origin/new_plugin_system' into merge-wi…
ludomikula Feb 3, 2024
d0b2b09
new: merge with latest LC
ludomikula Feb 5, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ client/node_modules/
client/packages/lowcoder-plugin-demo/.yarn/install-state.gz
client/packages/lowcoder-plugin-demo/yarn.lock
client/packages/lowcoder-plugin-demo/.yarn/cache/@types-node-npm-16.18.68-56f72825c0-094ae9ed80.zip
.DS_Store
.DS_Store
application-dev.yml
24 changes: 15 additions & 9 deletions deploy/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@
## Build Lowcoder api-service application
##
FROM maven:3.9-eclipse-temurin-17 AS build-api-service

# Clone and build lowcoder-plugin-api
COPY ./lowcoder-plugin-api /build/plugin-api
WORKDIR /build/plugin-api
RUN --mount=type=cache,target=/root/.m2 mvn -f pom.xml clean install -DskipTests

COPY ./server/api-service /lowcoder-server
WORKDIR /lowcoder-server
RUN --mount=type=cache,target=/root/.m2 mvn -f pom.xml clean package -DskipTests

# Create required folder structure
RUN mkdir -p /lowcoder/api-service/plugins /lowcoder/api-service/config /lowcoder/api-service/logs

# Define lowcoder main jar and plugin jars
ARG JAR_FILE=/lowcoder-server/lowcoder-server/target/lowcoder-server-*.jar
ARG PLUGIN_JARS=/lowcoder-server/lowcoder-plugins/*/target/*.jar

# Copy lowcoder server application and plugins
RUN cp ${JAR_FILE} /lowcoder/api-service/server.jar \
&& cp ${PLUGIN_JARS} /lowcoder/api-service/plugins/
RUN mkdir -p /lowcoder/api-service/config /lowcoder/api-service/logs /lowcoder/plugins

# Copy lowcoder server configuration
COPY server/api-service/lowcoder-server/src/main/resources/selfhost/ce/application.yml /lowcoder/api-service/config/
Expand Down Expand Up @@ -43,6 +41,13 @@ RUN apt-get update && apt-get install -y --no-install-recommends gosu \
# Copy lowcoder server configuration
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder/api-service /lowcoder/api-service

# Copy lowcoder api service app, dependencies and libs
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/app /lowcoder/api-service/app
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/dependencies /lowcoder/api-service/dependencies
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/libs /lowcoder/api-service/libs
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/plugins /lowcoder/api-service/plugins
COPY --chown=lowcoder:lowcoder --from=build-api-service /lowcoder-server/distribution/target/lowcoder-api-service-bin/set-classpath.sh /lowcoder/api-service/set-classpath.sh

EXPOSE 8080
CMD [ "sh" , "/lowcoder/api-service/entrypoint.sh" ]

Expand Down Expand Up @@ -202,6 +207,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install --no-instal

# Add lowcoder api-service
COPY --chown=lowcoder:lowcoder --from=lowcoder-ce-api-service /lowcoder/api-service /lowcoder/api-service
RUN mkdir -p /lowcoder/plugins/ && chown lowcoder:lowcoder /lowcoder/plugins/

# Add lowcoder node-service
COPY --chown=lowcoder:lowcoder --from=lowcoder-ce-node-service /lowcoder/node-service /lowcoder/node-service
Expand Down
6 changes: 5 additions & 1 deletion deploy/docker/api-service/entrypoint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,16 @@ ${JAVA_HOME}/bin/java -version
echo

cd /lowcoder/api-service
source set-classpath.sh

exec gosu ${USER_ID}:${GROUP_ID} ${JAVA_HOME}/bin/java \
-Djava.util.prefs.userRoot=/tmp \
-Djava.security.egd=file:/dev/./urandom \
-Dhttps.protocols=TLSv1.1,TLSv1.2 \
-Dlog4j2.formatMsgNoLookups=true \
-Dspring.config.location="file:///lowcoder/api-service/config/application.yml,file:///lowcoder/api-service/config/application-selfhost.yml" \
--add-opens java.base/java.nio=ALL-UNNAMED \
-cp "${LOWCODER_CLASSPATH:=.}" \
${JAVA_OPTS} \
-jar "${APP_JAR}" --spring.webflux.base-path=${CONTEXT_PATH} ${CUSTOM_APP_PROPERTIES}
org.lowcoder.api.ServerApplication --spring.webflux.base-path=${CONTEXT_PATH} ${CUSTOM_APP_PROPERTIES}

6 changes: 3 additions & 3 deletions server/api-service/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ dependency-reduced-pom.xml
.run/**
logs/**
tmp/**
/openblocks-server/logs/

# Ignore plugin.properties which are generated dynamically
**/plugin.properties

# to ignore the node_modeules folder
node_modules
Expand All @@ -34,5 +35,4 @@ package-lock.json
# test coverage
coverage-summary.json
app/client/cypress/locators/Widgets.json
/openblocks-domain/logs/
application-lowcoder.yml
application-lowcoder.yml
122 changes: 122 additions & 0 deletions server/api-service/PLUGIN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Lowcoder plugin system (WIP)

This is an ongoing effort to refactor current plugin system based on pf4j library.

## Reasoning

1. create a cleaner and simpler plugin system with clearly defined purpose(s) (new endpoints, new datasource types, etc..)
2. lowcoder does not need live plugin loading/reloading/unloading/updates, therefore the main feature of pf4j is rendered useless, in fact it adds a lot of complexity due to classloaders used for managing plugins (especially in spring/boot applications)
3. simpler and easier plugin detection - just a jar with a class implementing a common interface (be it a simple pojo project or a complex spring/boot implementation)

## How it works

The main entrypoint for plugin system is in **lowcoder-server** module with class **org.lowcoder.api.framework.configuration.PluginConfiguration**
It creates:
- LowcoderPluginManager bean which is responsible for plugin lifecycle management
- Adds plugin defined endpoints to lowcoder by creating **pluginEndpoints** bean
- TODO: Adds plugin defined datasources to lowcoder by creating **pluginDatasources** bean

### lowcoder-plugin-api library

This library contains APIs for plugin implementations.
It is used by both, lowcoder API server as well as all plugins.

### PluginLoader

The sole purpose of a PluginLoader is to find plugin candidates and load them into VM.
There is currently one implementation that based on paths - **PathBasedPluginLoader**, it:
- looks in folders and subfolders defined in **application.yaml** - entries can point to a folder or specific jar file. If a relative path is supplied, the location of lowcoder API server application jar is used as parent folder (when run in non-packaged state, eg. in IDE, it uses the folder where ServerApplication.class is generated)

```yaml
common:
plugin-dirs:
- plugins
- /some/custom/path/myGreatPlugin.jar
```
- finds all **jar**(s) and inspects them for classes implementing **LowcoderPlugin** interface
- instantiates all LowcoderPlugin implementations

### LowcoderPluginManager

The main job of plugin manager is to:
- register plugins found and instantiated by **PluginLoader**
- start registered plugins by calling **LowcoderPlugin.load()** method
- create and register **RouterFunction**(s) for all loaded plugin endpoints
- TODO: create and register datasources for all loaded plugin datasources

## Plugin project structure

Plugin jar can be structured in any way you like. It can be a plain java project, but also a spring/boot based project or based on any other framework.

It is composed from several parts:
- class(es) implementing **LowcoderPlugin** interface
- class(es) implementing **LowcoderEndpoint** interface, containing endpoint handler functions marked with **@EndpointExtension** annotation. These functions must obey following format:

```java
@EndpointExtension(uri = <endpoint uri>, method = <HTTP method>)
public Mono<ServerResponse> <handler name>(ServerRequest request)
{
... your endpoint logic implementation
}

for example:

@EndpointExtension(uri = "/hello-world", method = Method.GET)
public Mono<ServerResponse> helloWorld(ServerRequest request)
{
return ServerResponse.ok().body(Mono.just(Hello.builder().message("Hello world!").build()), Hello.class);
}
```
- TODO: class(es) impelemting **LowcoderDatasource** interface

### LowcoderPlugin implementations

Methods of interest:
- **pluginId()** - unique plugin ID - if a plugin with such ID is already loaded, subsequent plugins whith this ID will be ignored
- **description()** - short plugin description
- **load(ApplicationContext parentContext)** - is called during plugin startup - this is the place where you should completely initialize your plugin. If initialization fails, return false
- **unload()** - is called during lowcoder API server shutdown - this is the place where you should release all resources
- **endpoints()** - needs to contain all initialized **PluginEndpoints** you want to expose, for example:

```java
@Override
public List<PluginEndpoint> endpoints()
{
List<PluginEndpoint> endpoints = new ArrayList<>();

endpoints.add(new HelloWorldEndpoint());

return endpoints;
}
```
- **pluginInfo()** - should return a record object with additional information about your plugin. It is serialized to JSON as part of the **/plugins** listing (see **"info"** object in this example):

```json
[
{
"id": "example-plugin",
"description": "Example plugin for lowcoder platform",
"info": {}
},
{
"id": "enterprise",
"description": "Lowcoder enterprise plugin",
"info": {
"enabledFeatures": [
"endpointApiUsage"
]
}
}
]
```

## TODOs

1. Implement endpoint security - currently all plugin endpoints are public (probably by adding **security** attribute to **@EndpointExtension** and enforcing it)


## QUESTIONS / CONSIDERATIONS

1. currently the plugin endpoints are prefixed with **/plugin/{pluginId}/** - this is hardcoded, do we want to make it configurable?


84 changes: 84 additions & 0 deletions server/api-service/distribution/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-root</artifactId>
<version>${revision}</version>
</parent>

<artifactId>distribution</artifactId>
<packaging>pom</packaging>

<properties>
<assembly.lib.directory>${project.build.directory}/dependencies</assembly.lib.directory>
</properties>


<!-- Dependency added here only to make sure this module is built after
everything alse was built -->
<dependencies>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-sdk</artifactId>
</dependency>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-infra</artifactId>
</dependency>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-domain</artifactId>
</dependency>
<dependency>
<groupId>org.lowcoder</groupId>
<artifactId>lowcoder-server</artifactId>
</dependency>
</dependencies>

<build>
<finalName>lowcoder-api-service</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>${assembly.lib.directory}</outputDirectory>
<overWriteReleases>false</overWriteReleases>
<overWriteSnapshots>false</overWriteSnapshots>
<overWriteIfNewer>true</overWriteIfNewer>
<prependGroupId>true</prependGroupId>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<id>distro-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<attach>false</attach>
<descriptors>
<descriptor>src/assembly/bin.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

</project>
72 changes: 72 additions & 0 deletions server/api-service/distribution/src/assembly/bin.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.2.0 http://maven.apache.org/xsd/assembly-2.2.0.xsd">
<id>bin</id>
<formats>
<format>dir</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>

<files>
<file>
<source>src/assembly/set-classpath.sh</source>
<outputDirectory></outputDirectory>
</file>
</files>
<fileSets>
<fileSet>
<directory>${assembly.lib.directory}</directory>
<outputDirectory>dependencies</outputDirectory>
<excludes>
<exclude>${project.groupId}:*</exclude>
</excludes>
</fileSet>
</fileSets>

<moduleSets>
<!-- Main lowcoder API server application -->
<moduleSet>
<useAllReactorProjects>true</useAllReactorProjects>
<includes>
<include>org.lowcoder:lowcoder-server</include>
</includes>
<binaries>
<outputDirectory>app</outputDirectory>
<includeDependencies>false</includeDependencies>
<unpack>false</unpack>
</binaries>
</moduleSet>

<!-- Lowcoder API server dependencies -->
<moduleSet>
<useAllReactorProjects>true</useAllReactorProjects>
<includes>
<include>org.lowcoder:lowcoder-domain</include>
<include>org.lowcoder:lowcoder-infra</include>
<include>org.lowcoder:lowcoder-sdk</include>
</includes>
<binaries>
<outputDirectory>libs</outputDirectory>
<includeDependencies>false</includeDependencies>
<unpack>false</unpack>
</binaries>
</moduleSet>

<!-- Lowcoder plugins -->
<moduleSet>
<useAllReactorProjects>true</useAllReactorProjects>
<includeSubModules>true</includeSubModules>
<includes>
<include>org.lowcoder:*Plugin</include>
</includes>
<excludes>
<exclude>org.lowcoder:sqlBasedPlugin</exclude>
</excludes>
<binaries>
<outputDirectory>plugins</outputDirectory>
<includeDependencies>false</includeDependencies>
<unpack>false</unpack>
</binaries>
</moduleSet>
</moduleSets>
</assembly>
11 changes: 11 additions & 0 deletions server/api-service/distribution/src/assembly/set-classpath.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

#
# Set lowcoder api service classpath for use in startup script
#
export LOWCODER_CLASSPATH="`find libs/ dependencies/ app/ -type f -name "*.jar" | tr '\n' ':' | sed -e 's/:$//'`"

#
# Example usage:
#
# java -cp "${LOWCODER_CLASSPATH}" org.lowcoder.api.ServerApplication
Loading