fix ub and add deleting plugins
This commit is contained in:
@@ -12,6 +12,12 @@ The `Plugin` interface is the main interface for plugins to implement. It has th
|
|||||||
- `Name()` - This method returns the name of the plugin.
|
- `Name()` - This method returns the name of the plugin.
|
||||||
- `RegisterRoutes(router fiber.Router)` - This method is called when the plugin is loaded and is responsible for registering routes to the router.
|
- `RegisterRoutes(router fiber.Router)` - This method is called when the plugin is loaded and is responsible for registering routes to the router.
|
||||||
|
|
||||||
|
Furthermore, your plugin should export a symbol named `Plugin` that implements the `Plugin` interface. The easiest way to do this in Go is simply
|
||||||
|
|
||||||
|
```go
|
||||||
|
var Plugin MyPluginDataStruct
|
||||||
|
```
|
||||||
|
|
||||||
An example plugin is provided in the `plugin` directory and can be built using the following command:
|
An example plugin is provided in the `plugin` directory and can be built using the following command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -22,10 +28,11 @@ This will generate a `plugin.so` file in the `plugin` directory.
|
|||||||
|
|
||||||
## RPC
|
## RPC
|
||||||
|
|
||||||
GLoom exposes an RPC server on port 7143. This server is used for GLoom's plugin management system. Gloom currently provides two methods:
|
GLoom exposes an RPC server on port 7143. This server is used for GLoom's plugin management system. Gloom currently provides three methods:
|
||||||
|
|
||||||
- `ListPlugins(struct{}, reply *[]PluginData) error` - This method returns a list of all registered plugins and their domains.
|
- `ListPlugins(struct{}, reply *[]PluginData) error` - This method returns a list of all registered plugins and their domains.
|
||||||
- `UploadPlugin(PluginUpload, reply *string) error` - This method uploads a plugin to GLoom.
|
- `UploadPlugin(PluginUpload, reply *string) error` - This method uploads a plugin to GLoom.
|
||||||
|
- `DeletePlugin(pluginName string, reply *string) error` - This method deletes a plugin from GLoom.
|
||||||
|
|
||||||
PluginData is a struct that looks like this:
|
PluginData is a struct that looks like this:
|
||||||
|
|
||||||
@@ -54,3 +61,4 @@ GLoomI is the included plugin management interface for GLoom, it utilizes the GL
|
|||||||
- `POST /api/plugins` - This endpoint uploads a plugin to GLoom. it takes a multipart/form-data request with the following fields:
|
- `POST /api/plugins` - This endpoint uploads a plugin to GLoom. it takes a multipart/form-data request with the following fields:
|
||||||
- `plugin` - The plugin file to upload.
|
- `plugin` - The plugin file to upload.
|
||||||
- `domains` - A comma-separated list of domains to associate with the plugin.
|
- `domains` - A comma-separated list of domains to associate with the plugin.
|
||||||
|
- `DELETE /api/plugins/:pluginName` - This endpoint deletes a plugin from GLoom. `pluginName` is the string returned by the `Name()` method of the plugin and is case-sensitive.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# GLoom
|
# GLoom
|
||||||
|
|
||||||
GLoom is a plugin-based web server written in Go. GLoom's focus is to provide and simple and efficient way to host micro-web apps easily.
|
GLoom is a plugin-based web app manager written in Go. GLoom's focus is to provide and simple and efficient way to host micro-web apps easily.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,18 @@ func (p *GLoomI) RegisterRoutes(router fiber.Router) {
|
|||||||
|
|
||||||
return c.Status(fiber.StatusOK).SendString("Plugin uploaded successfully")
|
return c.Status(fiber.StatusOK).SendString("Plugin uploaded successfully")
|
||||||
})
|
})
|
||||||
|
|
||||||
|
apiRouter.Delete("/plugins/:pluginName", func(c fiber.Ctx) error {
|
||||||
|
pluginName := c.Params("pluginName")
|
||||||
|
var response string
|
||||||
|
err := p.client.Call("GloomRPC.DeletePlugin", pluginName, &response)
|
||||||
|
if err != nil {
|
||||||
|
return c.Status(fiber.StatusInternalServerError).SendString("Failed to list plugins: " + err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Status(fiber.StatusOK).SendString(response)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
86
main.go
86
main.go
@@ -30,6 +30,7 @@ type Plugin interface {
|
|||||||
type PluginInstance struct {
|
type PluginInstance struct {
|
||||||
Plugin Plugin
|
Plugin Plugin
|
||||||
Name string
|
Name string
|
||||||
|
Path string
|
||||||
Router *fiber.App
|
Router *fiber.App
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,6 +122,7 @@ func (gloom *GLoom) RegisterPlugin(pluginPath string, domains []string) {
|
|||||||
pluginInstance := PluginInstance{
|
pluginInstance := PluginInstance{
|
||||||
Plugin: pluginLib,
|
Plugin: pluginLib,
|
||||||
Name: pluginLib.Name(),
|
Name: pluginLib.Name(),
|
||||||
|
Path: pluginPath,
|
||||||
Router: router,
|
Router: router,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,6 +133,15 @@ func (gloom *GLoom) RegisterPlugin(pluginPath string, domains []string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (gloom *GLoom) DeletePlugin(pluginName string) {
|
||||||
|
gloom.domainMap.Range(func(domain string, plugin *PluginInstance) bool {
|
||||||
|
if plugin.Name == pluginName {
|
||||||
|
gloom.domainMap.Delete(domain)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (gloom *GLoom) StartRPCServer() error {
|
func (gloom *GLoom) StartRPCServer() error {
|
||||||
rpcServer := &GloomRPC{gloom: gloom}
|
rpcServer := &GloomRPC{gloom: gloom}
|
||||||
err := rpc.Register(rpcServer)
|
err := rpc.Register(rpcServer)
|
||||||
@@ -197,11 +208,57 @@ type PluginUpload struct {
|
|||||||
|
|
||||||
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
|
func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
|
||||||
slog.Info("Uploading plugin", "plugin", plugin.Name, "domains", plugin.Domains)
|
slog.Info("Uploading plugin", "plugin", plugin.Name, "domains", plugin.Domains)
|
||||||
|
var plugExists bool
|
||||||
|
rpc.gloom.DB.QueryRow("SELECT path FROM plugins WHERE path = ?", "plugs/"+plugin.Name).Scan(&plugExists)
|
||||||
|
|
||||||
|
var domains []string
|
||||||
|
if plugExists {
|
||||||
|
// if plugin exists, we need to not check for domains that this plug has already registered, but instead check for new domains this plugin is registering
|
||||||
|
domains = make([]string, 0)
|
||||||
|
var existingDomains []string
|
||||||
|
err := rpc.gloom.DB.QueryRow("SELECT domains FROM plugins WHERE path = ?", "plugs/"+plugin.Name).Scan(&existingDomains)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range existingDomains {
|
||||||
|
var found bool
|
||||||
|
for _, domainToCheck := range plugin.Domains {
|
||||||
|
if domain == domainToCheck {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
domains = append(domains, domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
found = false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
domains = plugin.Domains
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, domain := range domains {
|
||||||
|
_, ok := rpc.gloom.domainMap.Load(domain)
|
||||||
|
if ok {
|
||||||
|
*reply = fmt.Sprintf("Domain %s already exists", domain)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// regardless of if plugin exists or not, we'll upload the file since this could be an update to an existing plugin
|
||||||
if err := os.WriteFile(fmt.Sprintf("plugs/%s", plugin.Name), plugin.Data, 0644); err != nil {
|
if err := os.WriteFile(fmt.Sprintf("plugs/%s", plugin.Name), plugin.Data, 0644); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Print("Plugin uploaded successfully")
|
fmt.Println("Plugin uploaded successfully")
|
||||||
|
|
||||||
|
if plugExists {
|
||||||
|
// exit out early otherwise we risk creating multiple of the same plugin and causing undefined behavior
|
||||||
|
*reply = "Plugin updated successfully"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
rpc.gloom.DB.Exec("INSERT INTO plugins (path, domains) VALUES (?, ?)", "plugs/"+plugin.Name, strings.Join(plugin.Domains, ","))
|
rpc.gloom.DB.Exec("INSERT INTO plugins (path, domains) VALUES (?, ?)", "plugs/"+plugin.Name, strings.Join(plugin.Domains, ","))
|
||||||
rpc.gloom.RegisterPlugin("plugs/"+plugin.Name, plugin.Domains)
|
rpc.gloom.RegisterPlugin("plugs/"+plugin.Name, plugin.Domains)
|
||||||
@@ -209,6 +266,33 @@ func (rpc *GloomRPC) UploadPlugin(plugin PluginUpload, reply *string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rpc *GloomRPC) DeletePlugin(pluginName string, reply *string) error {
|
||||||
|
var targetPlugin PluginInstance
|
||||||
|
for _, plugin := range rpc.gloom.Plugins {
|
||||||
|
if plugin.Name == pluginName {
|
||||||
|
targetPlugin = plugin
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := rpc.gloom.DB.Exec("DELETE FROM plugins WHERE path = ?", targetPlugin.Path)
|
||||||
|
if err != nil {
|
||||||
|
*reply = "Plugin not found"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Remove(targetPlugin.Path)
|
||||||
|
if err != nil {
|
||||||
|
*reply = "Plugin not found"
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rpc.gloom.DeletePlugin(pluginName)
|
||||||
|
|
||||||
|
*reply = "Plugin deleted successfully"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
if err := godotenv.Load(); err != nil {
|
if err := godotenv.Load(); err != nil {
|
||||||
fmt.Println("No .env file found")
|
fmt.Println("No .env file found")
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
CREATE TABLE IF NOT EXISTS plugins (
|
CREATE TABLE IF NOT EXISTS plugins (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
path TEXT NOT NULL,
|
path TEXT NOT NULL UNIQUE,
|
||||||
domains TEXT NOT NULL
|
domains TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user