Plugins work now oughghgghughgghg

This commit is contained in:
Sasha Koshka 2024-12-10 15:51:34 -05:00
parent c2ccaff8ab
commit f112a2e564
4 changed files with 121 additions and 66 deletions

View File

@ -129,29 +129,31 @@ func main () {
Config: config, Config: config,
} }
environment.Providers = providers.All() environment.Providers = providers.All()
err := environment.Init(context.Background())
if err != nil { log.Fatal(err) }
// load plugins // load plugins
for _, pat := range pluginPath { var err error
entries, err := os.ReadDir(pat) var plugins []step.Provider
if err != nil { continue } if flagUnsafePlugins.Value == "true" {
for _, entry := range entries { plugins, err = step.LoadAllProviderPluginsUnsafe(pluginPath...)
pluginPath := filepath.Join(pat, entry.Name()) } else {
ext := filepath.Ext(pluginPath) plugins, err = step.LoadAllProviderPlugins(pluginPath...)
if ext != ".so" { continue } }
if flagUnsafePlugins.Value == "true" { if err != nil {
_, err = environment.LoadProviderPluginUnsafe(pluginPath) if errs, ok := err.(step.Errors); ok {
} else { for _, err := range errs.Unwrap() {
_, err = environment.LoadProviderPlugin(pluginPath)
}
if err != nil {
log.Println("!!!", err) log.Println("!!!", err)
} }
} else {
log.Println("!!!", err)
} }
} }
environment.Providers = append(environment.Providers, plugins...)
logProviders(environment.Providers) logProviders(environment.Providers)
// initialize the environment
err = environment.Init(context.Background())
if err != nil { log.Fatal(err) }
// set up the HTTP handler // set up the HTTP handler
handler := stephttp.Handler { handler := stephttp.Handler {
Environment: &environment, Environment: &environment,

View File

@ -5,9 +5,7 @@ import "io"
import "fmt" import "fmt"
import "time" import "time"
import "io/fs" import "io/fs"
import "plugin"
import "errors" import "errors"
import "syscall"
import "context" import "context"
import "path/filepath" import "path/filepath"
import "html/template" import "html/template"
@ -105,55 +103,6 @@ func (this *Environment) Unload (name string) {
delete(documents, name) delete(documents, name)
} }
// LoadProviderPlugin loads a plugin given its file path. The file must:
//
// - Be a shared library
// - Be built with go -buildmode=plugin
// - Be built with the same version of Go
// - Be built with the same version of STEP
// - Be owned by root
//
// Plugins cannot be unloaded from the current program once they are loaded.
// Sorgy :( its Go's fault.
func (this *Environment) LoadProviderPlugin (name string) (Provider, error) {
return this.loadProviderPlugin(name, true)
}
// LoadProviderPluginUnsafe is like LoadProviderPlugin, but does not check to
// see that the file is owned by root, thereby making it easier to run a random
// plugin you just compiled. This should not be used otherwise.
func (this *Environment) LoadProviderPluginUnsafe (name string) (Provider, error) {
return this.loadProviderPlugin(name, false)
}
func (this *Environment) loadProviderPlugin (name string, checkRoot bool) (Provider, error) {
plugin, err := this.loadPlugin(name, checkRoot)
if err != nil { return nil, err }
providerSymbol, err := plugin.Lookup("NewProvider")
if err != nil { return nil, err }
providerFactory, ok := providerSymbol.(func () Provider)
if !ok { return nil, ErrPluginBadSymbol }
provider := providerFactory()
this.Providers = append(this.Providers, provider)
return provider, nil
}
func (this *Environment) loadPlugin (name string, checkRoot bool) (*plugin.Plugin, error) {
name = filepath.Clean(name)
if checkRoot {
info, err := os.Stat(name)
if err != nil { return nil, err }
if info, ok := info.Sys().(*syscall.Stat_t); ok {
if info.Uid != 0 {
return nil, ErrPluginNotOwnedByRoot
}
} else {
return nil, ErrInsufficientSystem
}
}
return plugin.Open(name)
}
func (this *Environment) parse (name string, modTime time.Time, input io.Reader) (*Document, error) { func (this *Environment) parse (name string, modTime time.Time, input io.Reader) (*Document, error) {
documents, done := this.documents.Borrow() documents, done := this.documents.Borrow()
defer done() defer done()

View File

@ -8,6 +8,7 @@ type Error string; const (
ErrTypeMismatch Error = "type mismatch" ErrTypeMismatch Error = "type mismatch"
ErrPluginBadSymbol Error = "plugin has an incorrect symbol" ErrPluginBadSymbol Error = "plugin has an incorrect symbol"
ErrPluginNotOwnedByRoot Error = "plugin is not owned by the root user" ErrPluginNotOwnedByRoot Error = "plugin is not owned by the root user"
ErrPathNotAbsolute Error = "path is not absolute"
ErrInsufficientSystem Error = "the system cannot perform this action" ErrInsufficientSystem Error = "the system cannot perform this action"
) )
@ -15,3 +16,8 @@ type Error string; const (
func (err Error) Error () string { func (err Error) Error () string {
return string(err) return string(err)
} }
// Errors is any error that unwraps to a list of sub-errors.
type Errors interface {
Unwrap () []error
}

98
plugin.go Normal file
View File

@ -0,0 +1,98 @@
package step
import "os"
import "errors"
import "plugin"
import "syscall"
import "path/filepath"
// LoadAllProviderPlugins loads all plugins according to the given search path.
//
// If a non-nil error is returned, the error should be treated as a list of
// warning messages and the return value should be used normally.
func LoadAllProviderPlugins (pluginPath ...string) ([]Provider, error) {
return loadAllProviderPlugins(pluginPath, true)
}
// LoadAllProviderPluginsUnsafe is like LoadAllProviderPlugins, but does not
// check to see that the files are owned by root, thereby making it easier to
// run a random plugin you just compiled. This should not be used otherwise.
//
// If a non-nil error is returned, the error should be treated as a list of
// warning messages and the return value should be used normally.
func LoadAllProviderPluginsUnsafe (pluginPath ...string) ([]Provider, error) {
return loadAllProviderPlugins(pluginPath, false)
}
func loadAllProviderPlugins (pluginPath []string, checkRoot bool) ([]Provider, error) {
providers := []Provider { }
errs := []error { }
for _, pat := range pluginPath {
entries, err := os.ReadDir(pat)
if err != nil { continue }
for _, entry := range entries {
pluginPath := filepath.Join(pat, entry.Name())
ext := filepath.Ext(pluginPath)
if ext != ".so" { continue }
provider, err := loadProviderPlugin(pluginPath, checkRoot)
if err != nil {
errs = append(errs, err)
continue
}
providers = append(providers, provider)
}
}
return providers, errors.Join(errs...)
}
// LoadProviderPlugin loads a plugin given its file path. The file must:
//
// - Be a shared library
// - Be addressed with an absolute path
// - Be built with go -buildmode=plugin
// - Be built with the same version of Go
// - Be built with the same version of STEP
// - Be owned by root
//
// Plugins cannot be unloaded from the current program once they are loaded.
// Sorgy :( its Go's fault.
func LoadProviderPlugin (name string) (Provider, error) {
return loadProviderPlugin(name, true)
}
// LoadProviderPluginUnsafe is like LoadProviderPlugin, but does not check to
// see that the file is owned by root, thereby making it easier to run a random
// plugin you just compiled. This should not be used otherwise.
func LoadProviderPluginUnsafe (name string) (Provider, error) {
return loadProviderPlugin(name, false)
}
func loadProviderPlugin (name string, checkRoot bool) (Provider, error) {
plugin, err := loadPlugin(name, checkRoot)
if err != nil { return nil, err }
providerSymbol, err := plugin.Lookup("NewProvider")
if err != nil { return nil, err }
providerFactory, ok := providerSymbol.(func () Provider)
if !ok { return nil, ErrPluginBadSymbol }
provider := providerFactory()
return provider, nil
}
func loadPlugin (name string, checkRoot bool) (*plugin.Plugin, error) {
name = filepath.Clean(name)
if !filepath.IsAbs(name) {
return nil, ErrPathNotAbsolute
}
if checkRoot {
info, err := os.Stat(name)
if err != nil { return nil, err }
if info, ok := info.Sys().(*syscall.Stat_t); ok {
if info.Uid != 0 {
return nil, ErrPluginNotOwnedByRoot
}
} else {
return nil, ErrInsufficientSystem
}
}
return plugin.Open(name)
}