3
0

Initial support for plugins

This commit is contained in:
Denis Arh 2021-11-02 18:45:08 +01:00
parent 49b5ad83b9
commit 614d2b3015
10 changed files with 269 additions and 5 deletions

View File

@ -5,7 +5,9 @@ import (
"net/http"
"github.com/cortezaproject/corteza-server/auth/settings"
"github.com/cortezaproject/corteza-server/pkg/logger"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/plugin"
"github.com/cortezaproject/corteza-server/store"
"github.com/go-chi/chi"
"github.com/spf13/cobra"
@ -44,6 +46,9 @@ type (
lvl int
Log *zap.Logger
// Available plugins
plugins plugin.Set
// Store interface
//
// Just a blank interface{} because we want to avoid generating
@ -71,7 +76,7 @@ type (
func New() *CortezaApp {
app := &CortezaApp{
lvl: bootLevelWaiting,
Log: zap.NewNop(),
Log: logger.Default(),
}
app.InitCLI()

View File

@ -54,8 +54,6 @@ const (
// Setup configures all required services
func (app *CortezaApp) Setup() (err error) {
app.Log = logger.Default()
if app.lvl >= bootLevelSetup {
// Are basics already set-up?
return nil
@ -147,6 +145,10 @@ func (app *CortezaApp) Setup() (err error) {
}
}
if err = app.plugins.Setup(app.Log.Named("plugin")); err != nil {
return
}
app.lvl = bootLevelSetup
return
}
@ -414,6 +416,14 @@ func (app *CortezaApp) InitServices(ctx context.Context) (err error) {
// Initializing seeder
_ = seeder.Seeder(ctx, app.Store, seeder.Faker())
if err = app.plugins.Initialize(ctx, app.Log); err != nil {
return
}
if err = app.plugins.RegisterAutomation(autService.Registry()); err != nil {
return
}
app.lvl = bootLevelServicesInitialized
return
}

View File

@ -8,9 +8,11 @@ import (
federationCommands "github.com/cortezaproject/corteza-server/federation/commands"
"github.com/cortezaproject/corteza-server/pkg/cli"
"github.com/cortezaproject/corteza-server/pkg/options"
"github.com/cortezaproject/corteza-server/pkg/plugin"
fakerCommands "github.com/cortezaproject/corteza-server/pkg/seeder/commands"
"github.com/cortezaproject/corteza-server/store"
systemCommands "github.com/cortezaproject/corteza-server/system/commands"
"go.uber.org/zap"
)
// InitCLI function initializes basic Corteza subsystems
@ -24,7 +26,7 @@ func (app *CortezaApp) InitCLI() {
envs []string
)
app.Command = cli.RootCommand(func() error {
app.Command = cli.RootCommand(func() (err error) {
if len(envs) == 0 {
envs = []string{"."}
}
@ -37,7 +39,23 @@ func (app *CortezaApp) InitCLI() {
// loaded at this point!
app.Opt = options.Init()
return nil
app.Log.Warn("loading plugins", zap.String("paths", app.Opt.Plugins.Paths))
if app.Opt.Plugins.Enabled {
var paths []string
paths, err = plugin.Resolve(app.Opt.Plugins.Paths)
app.Log.Warn("loading plugins", zap.Strings("paths", paths))
app.plugins, err = plugin.Load(paths...)
if err != nil {
return err
}
} else {
// Empty set of plugins
app.plugins = plugin.Set{}
}
return err
})
app.Command.Flags().StringSliceVar(&envs, "env-file", nil,

View File

@ -26,6 +26,7 @@ type (
RBAC RBACOpt
Locale LocaleOpt
Limit LimitOpt
Plugins PluginsOpt
}
)
@ -55,5 +56,6 @@ func Init() *Options {
RBAC: *RBAC(),
Locale: *Locale(),
Limit: *Limit(),
Plugins: *Plugins(),
}
}

36
pkg/options/plugins.gen.go generated Normal file
View File

@ -0,0 +1,36 @@
package options
// This file is auto-generated.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
//
// Definitions file that controls how this file is generated:
// pkg/options/plugins.yaml
type (
PluginsOpt struct {
Enabled bool `env:"PLUGINS_ENABLED"`
Paths string `env:"PLUGINS_PATHS"`
}
)
// Plugins initializes and returns a PluginsOpt with default values
func Plugins() (o *PluginsOpt) {
o = &PluginsOpt{
Enabled: true,
}
fill(o)
// Function that allows access to custom logic inside the parent function.
// The custom logic in the other file should be like:
// func (o *Plugins) Defaults() {...}
func(o interface{}) {
if def, ok := o.(interface{ Defaults() }); ok {
def.Defaults()
}
}(o)
return
}

12
pkg/options/plugins.yaml Normal file
View File

@ -0,0 +1,12 @@
docs:
title: Plugins
description: Server plugins
props:
- name: Enabled
type: bool
default: true
description: Enable plugins
- name: Paths
description: List of colon seperated paths or patterns where plugins could be found

29
pkg/plugin/automation.go Normal file
View File

@ -0,0 +1,29 @@
package plugin
// Collection of boot-lifecycle related functions
// that exec plugin functions
import (
"github.com/cortezaproject/corteza-server/automation/types"
sdk "github.com/cortezaproject/corteza-server/sdk/plugin"
)
type (
automationRegistry interface {
AddFunctions(ff ...*types.Function)
// AddTypes(tt ...expr.Type)
}
)
func (pp Set) RegisterAutomation(r automationRegistry) error {
for _, p := range pp {
d, is := p.def.(sdk.AutomationFunctionsProvider)
if !is {
continue
}
r.AddFunctions(d.AutomationFunctions()...)
}
return nil
}

43
pkg/plugin/boot.go Normal file
View File

@ -0,0 +1,43 @@
package plugin
// Collection of boot-lifecycle related functions
// that exec plugin functions
import (
"context"
sdk "github.com/cortezaproject/corteza-server/sdk/plugin"
"go.uber.org/zap"
)
func (pp Set) Setup(log *zap.Logger) error {
for _, p := range pp {
d, is := p.def.(sdk.Setup)
if !is {
continue
}
err := d.Setup(log)
if err != nil {
return err
}
}
return nil
}
func (pp Set) Initialize(ctx context.Context, log *zap.Logger) error {
for _, p := range pp {
d, is := p.def.(sdk.Initialize)
if !is {
continue
}
err := d.Initialize(ctx, log)
if err != nil {
return err
}
}
return nil
}

83
pkg/plugin/plugin.go Normal file
View File

@ -0,0 +1,83 @@
package plugin
import (
"fmt"
"os"
"path/filepath"
"plugin"
"strings"
)
type (
// Set of plugins
Set []*item
// item represents a plugin
item struct {
src string
def interface{}
}
)
// Resolve string with colon separated paths
func Resolve(paths string) (out []string, err error) {
var (
matches []string
)
for _, part := range strings.Split(paths, ":") {
matches, err = filepath.Glob(part)
if err != nil {
return
}
out = append(out, matches...)
}
return
}
// Load loads plugins from all given paths
func Load(paths ...string) (Set, error) {
var set = Set{}
for _, path := range paths {
if info, err := os.Lstat(path); err != nil {
return nil, err
} else if info.IsDir() {
return nil, fmt.Errorf("can not use directory %s as a plugin", path)
}
if i, err := load(path); err != nil {
return nil, err
} else {
set = append(set, i)
}
}
return set, nil
}
// load single plugin from the given path
func load(path string) (i *item, err error) {
i = &item{}
p, err := plugin.Open(path)
if err != nil {
return
}
aux, err := p.Lookup("CortezaPlugin")
if err != nil {
return
}
fn, is := aux.(func() interface{})
if !is {
return nil, fmt.Errorf("incompatible plugin definition")
}
i.def = fn()
return
}

26
sdk/plugin/plugin.go Normal file
View File

@ -0,0 +1,26 @@
package plugin
import (
"context"
"github.com/cortezaproject/corteza-server/automation/types"
"go.uber.org/zap"
)
type (
Setup interface {
Setup(log *zap.Logger) error
}
Initialize interface {
Initialize(ctx context.Context, log *zap.Logger) error
}
AutomationFunctionsProvider interface {
AutomationFunctions() []*types.Function
}
//AutomationTypesProvider interface {
// AutomationTypes() []*expr.Type
//}
)