Skip to content

Commit

Permalink
lol, another stash
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjarosch committed Mar 13, 2024
1 parent 8504f03 commit 09e2166
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 115 deletions.
107 changes: 62 additions & 45 deletions plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
type PluginType string

const (
// SymbolName defines the symbol which will be loaded from the shared library.
SymbolName string = "Plugin"
// PluginSymbolName defines the symbol which will be loaded from the shared library.
PluginSymbolName string = "Plugin"

OutputPlugin PluginType = "output"
)
Expand All @@ -26,87 +26,94 @@ var (
ErrUnconfigurablePlugin = fmt.Errorf("plugin cannot be configured, does not implement the 'ConfigurablePlugin' interface")
)

type PluginMetadataProvider interface {
Name() string
Type() PluginType
}

type ConfigurablePlugin interface {
// ConfigPointer must return a pointer (!) to the config struct of the plugin.
// The configuration will be unmarshalled using YAML into the given struct.
// Struct-tags can be used.
ConfigPointer() interface{}
// Configure is called after the configuration of the plugin is injected.
// The plugin can then configure whatever it needs.
Configure() error
}

type Plugin interface {
PluginMetadataProvider
Run() error
}
type (
// PluginConstructor is a function which needs to be exported with the symbol SymbolName by the plugins.
PluginConstructor func() Plugin
ConfigurablePlugin interface {
// ConfigPointer must return a pointer (!) to the config struct of the plugin.
// The configuration will be unmarshalled using YAML into the given struct.
// Struct-tags can be used.
ConfigPointer() interface{}
// Configure is called after the configuration of the plugin is injected.
// The plugin can then configure whatever it needs.
Configure() error
}
PluginMetadataProvider interface {
Name() string
Type() PluginType
}
Plugin interface {
PluginMetadataProvider
Run() error
}
)

type PluginManager struct {
plugins map[PluginType]map[string]Plugin
configuredPlugins map[PluginType]map[string][]Plugin
pluginPaths map[PluginType]map[string]string
plugins map[PluginType]map[string]Plugin
configuredPlugins map[PluginType]map[string][]Plugin
pluginConstructors map[PluginType]map[string]PluginConstructor
}

func NewPluginManager() *PluginManager {
pm := &PluginManager{
plugins: make(map[PluginType]map[string]Plugin),
pluginPaths: make(map[PluginType]map[string]string),
configuredPlugins: make(map[PluginType]map[string][]Plugin),
plugins: make(map[PluginType]map[string]Plugin),
pluginConstructors: make(map[PluginType]map[string]PluginConstructor),
configuredPlugins: make(map[PluginType]map[string][]Plugin),
}

// for every plugin type, create the sub-maps
for _, t := range []PluginType{OutputPlugin} {
pm.plugins[t] = make(map[string]Plugin)
pm.pluginPaths[t] = make(map[string]string)
pm.pluginConstructors[t] = make(map[string]PluginConstructor)
pm.configuredPlugins[t] = make(map[string][]Plugin)
}

return pm
}

func (pm *PluginManager) LoadPlugin(path string) error {
plugin, err := pm.loadPlugin(path)
pluginConstructor, err := pm.loadPluginConstructor(path)
if err != nil {
return err
}

plugin := pluginConstructor()
if err := pm.validateMetadata(plugin); err != nil {
return err
}

if _, exists := pm.plugins[plugin.Type()][plugin.Name()]; exists {
return fmt.Errorf("plugin %s already loaded", PluginID(plugin.Type(), plugin.Name()))
}

pm.plugins[plugin.Type()][plugin.Name()] = plugin
pm.pluginConstructors[plugin.Type()][plugin.Name()] = pluginConstructor

return nil
}

func (pm *PluginManager) loadPlugin(path string) (Plugin, error) {
func (pm *PluginManager) loadPluginConstructor(path string) (PluginConstructor, error) {
p, err := plugin.Open(path)
if err != nil {
return nil, fmt.Errorf("unable to open plugin: %w", err)
}

symbol, err := p.Lookup(SymbolName)
symbol, err := p.Lookup(PluginSymbolName)
if err != nil {
return nil, fmt.Errorf("unable to find symbol '%s': %w", SymbolName, err)
return nil, fmt.Errorf("unable to find symbol '%s': %w", PluginSymbolName, err)
}

loadedPlugin, validPlugin := symbol.(Plugin)
if !validPlugin {
return nil, fmt.Errorf("invalid plugin: does not implement Plugin interface")
}
a := symbol.(*PluginConstructor)
_ = a

if err := pm.validateMetadata(loadedPlugin); err != nil {
return nil, err
pluginConstructor, validPlugin := symbol.(*PluginConstructor)
if !validPlugin {
return nil, fmt.Errorf("invalid plugin: invalid symbol '%s', want=PluginConstructor, have=%T", PluginSymbolName, pluginConstructor)
}

return loadedPlugin, nil
return *pluginConstructor, nil
}

// TODO: maybe add a 'MustInitializePlugin' to check for the 'ConfigurePlugin' interface?
func (pm *PluginManager) ConfigurePlugin(typ PluginType, name string, config data.Value) error {
plugin, err := pm.GetPlugin(typ, name)
if err != nil {
Expand All @@ -123,18 +130,27 @@ func (pm *PluginManager) ConfigurePlugin(typ PluginType, name string, config dat

// If the config is a slice, it means that we need to have multiple instances
// of the plugin with different configs.
// WARN: how do we copy plugins? re-load? deep-copy?
if config.IsSlice() {
configs, _ := config.Slice()
for _, c := range configs {
err = pm.unmarshalConfig(iplugin, data.NewValue(c))
// create a new instance of the plugin and configure it using the data
plugin := pm.pluginConstructors[plugin.Type()][plugin.Name()]()
err = pm.unmarshalConfig(plugin.(ConfigurablePlugin), data.NewValue(c))
if err != nil {
return err
}

err = iplugin.Configure()
err = plugin.(ConfigurablePlugin).Configure()
if err != nil {
return fmt.Errorf("failed to configure plugin %s: %w", PluginID(plugin.Type(), plugin.Name()), err)
}

pm.configuredPlugins[typ][name] = append(pm.configuredPlugins[typ][name], plugin)

// TODO: remove
err = plugin.Run()
if err != nil {
return fmt.Errorf("failed to initialize %s: %w", PluginID(plugin.Type(), plugin.Name()), err)
return err
}
}
}
Expand All @@ -150,7 +166,8 @@ func (pm *PluginManager) ConfigurePlugin(typ PluginType, name string, config dat
if err != nil {
return fmt.Errorf("failed to initialize %s: %w", PluginID(plugin.Type(), plugin.Name()), err)
}
return nil

pm.configuredPlugins[typ][name] = append(pm.configuredPlugins[typ][name], plugin)
}

return nil
Expand Down
169 changes: 104 additions & 65 deletions plugins/output/copy/main.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
package main

import (
"github.com/davecgh/go-spew/spew"
"fmt"
"io"
"os"
"path/filepath"

"github.com/lukasjarosch/skipper"
"github.com/lukasjarosch/skipper/codec"
"github.com/lukasjarosch/skipper/data"
)

// Plugin symbol which is loaded by skipper.
// If this does not exist, the plugin cannot be used.
var Plugin = Copy{}

type Config struct {
SourcePaths []string `yaml:"sourcePaths"`
TargetPath string `yaml:"targetPath"`
// OverwriteChmod os.FileMode `yaml:"overwriteChmod,omitempty"`
Overwrite bool `yaml:"overwrite"`
}

func NewConfig(raw map[string]interface{}) (Config, error) {
Expand All @@ -34,84 +34,123 @@ func NewConfig(raw map[string]interface{}) (Config, error) {

var ConfigPath = data.NewPath("config.plugins.output.copy")

// Plugin symbol which is loaded by skipper.
// If this does not exist, the plugin cannot be used.
var Plugin = NewCopy()

type Copy struct {
config Config
}

// ConfigPointer returns a pointer to [Config] which is going to be used by skipper
// to automatically load the config into said pointer, nice.
func (copy *Copy) ConfigPointer() interface{} {
return &copy.config
func NewCopy() skipper.PluginConstructor {
return func() skipper.Plugin {
return &Copy{}
}
}

// Configure checks that all configured source-paths exist and are readable.
func (copy *Copy) Configure() error {
spew.Dump(copy.config)
// configValue, err := inventory.GetPath(ConfigPath)
// if err != nil {
// return fmt.Errorf("config path does not exist: %w", err)
// }
//
// // TODO: this should be done by the manager, not the plugin
// // The plugin supports multiple instances of its configuration
// switch typ := configValue.Raw.(type) {
// case map[string]interface{}:
// config, err := NewConfig(typ)
// if err != nil {
// return fmt.Errorf("cannot create config: %w", err)
// }
// copy.configs = append(copy.configs, config)
// break
// case []interface{}:
// for _, c := range typ {
// if _, ok := c.(map[string]interface{}); !ok {
// return fmt.Errorf("malformed config: expected slice of maps, got %T", c)
// }
// conf, err := NewConfig(c.(map[string]interface{}))
// if err != nil {
// return fmt.Errorf("cannot create config: %w", err)
// }
// copy.configs = append(copy.configs, conf)
// }
// break
// default:
// return fmt.Errorf("malformed config at '%s': either a slice of configs or a simple config is expected", ConfigPath)
// }
//
// // At ConfigPath there could be a slice of maps, indicating
// // multiple configurations of this plugin.
// if configs, ok := configValue.Raw.([]interface{}); ok {
// for _, c := range configs {
// if _, ok := c.(map[string]interface{}); !ok {
// return fmt.Errorf("malformed config: expected slice of maps, got %T", c)
// }
// conf, err := NewConfig(c.(map[string]interface{}))
// if err != nil {
// return fmt.Errorf("cannot create config: %w", err)
// }
// copy.configs = append(copy.configs, conf)
// }
// }
//
// // There could also be just one map with the plugin config
// else if c, ok := configValue.Raw.(map[string]interface{}); ok {
// config, err := NewConfig(c)
// if err != nil {
// return fmt.Errorf("cannot create config: %w", err)
// }
// copy.configs = append(copy.configs, config)
// }
// source-paths must be readable
for _, sourcePath := range copy.config.SourcePaths {
_, err := os.Open(sourcePath)
if err != nil {
return fmt.Errorf("cannot open source-path: %w", err)
}
}

return nil
}

func (copy *Copy) Run() error {
for _, source := range copy.config.SourcePaths {
err := copyST(source, copy.config.TargetPath)
if err != nil {
return err
}
}
return nil
}

// ConfigPointer returns a pointer to [Config] which is going to be used by skipper
// to automatically load the config into said pointer, nice.
func (copy *Copy) ConfigPointer() interface{} {
return &copy.config
}

func (copy *Copy) Name() string {
return "copy"
}

func (copy *Copy) Type() skipper.PluginType {
return skipper.OutputPlugin
}

// Copy recursively copies the contents of sourcePath to targetPath.
func copyST(sourcePath, targetPath string) error {
// Get the file information of the source
sourceInfo, err := os.Stat(sourcePath)
if err != nil {
return err
}

// If the source is a directory, call the directory copy function
if sourceInfo.IsDir() {
return copyDir(sourcePath, targetPath)
}

// If the source is a file, call the file copy function
return copyFile(sourcePath, targetPath)
}

// copyDir copies the contents of a directory from sourcePath to targetPath.
func copyDir(sourcePath, targetPath string) error {
// Create the target directory with the same permissions as source directory
sourceInfo, err := os.Stat(sourcePath)
if err != nil {
return err
}
err = os.MkdirAll(targetPath, sourceInfo.Mode())
if err != nil {
return err
}

// Read the contents of the source directory
entries, err := os.ReadDir(sourcePath)
if err != nil {
return err
}

// Recursively copy each entry in the source directory
for _, entry := range entries {
sourceEntryPath := filepath.Join(sourcePath, entry.Name())
targetEntryPath := filepath.Join(targetPath, entry.Name())
err = copyST(sourceEntryPath, targetEntryPath)
if err != nil {
return err
}
}

return nil
}

// copyFile copies a single file from sourcePath to targetPath.
func copyFile(sourcePath, targetPath string) error {
sourceFile, err := os.Open(sourcePath)
if err != nil {
return err
}
defer sourceFile.Close()

targetFile, err := os.Create(targetPath)
if err != nil {
return err
}
defer targetFile.Close()

_, err = io.Copy(targetFile, sourceFile)
if err != nil {
return err
}

return nil
}
Loading

0 comments on commit 09e2166

Please sign in to comment.