Skip to content

Commit

Permalink
Merge pull request #81 from gatewayd-io/implement-requires-field
Browse files Browse the repository at this point in the history
Implement `requires` field for checking if required plugins are loaded or not
  • Loading branch information
mostafa authored Jan 4, 2023
2 parents cc249ae + a37aeb5 commit 5027dec
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 39 deletions.
18 changes: 16 additions & 2 deletions cmd/config_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ func getPath(path string) string {
// return nil
// }

// verificationPolicy returns the verification policy for the plugin from config file.
// verificationPolicy returns the hook verification policy from plugin config file.
func verificationPolicy() plugin.Policy {
vPolicy := globalConfig.String("plugins.verificationPolicy")
vPolicy := pluginConfig.String("plugins.verificationPolicy")
verificationPolicy := plugin.PassDown // default
switch vPolicy {
case "ignore":
Expand All @@ -52,6 +52,20 @@ func verificationPolicy() plugin.Policy {
return verificationPolicy
}

// pluginCompatPolicy returns the plugin compatibility policy from plugin config file.
func pluginCompatPolicy() plugin.CompatPolicy {
vPolicy := pluginConfig.String("plugins.compatibilityPolicy")
compatPolicy := plugin.Strict // default
switch vPolicy {
case "strict":
compatPolicy = plugin.Strict
case "loose":
compatPolicy = plugin.Loose
}

return compatPolicy
}

// loggerConfig returns the logger config from config file.
func loggerConfig() logging.LoggerConfig {
cfg := logging.LoggerConfig{StartupMsg: true}
Expand Down
27 changes: 15 additions & 12 deletions cmd/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@ var runCmd = &cobra.Command{
}
}

// Load plugins and register their hooks
// Set the plugin compatibility policy.
pluginRegistry.CompatPolicy = pluginCompatPolicy()

// Load plugins and register their hooks.
pluginRegistry.LoadPlugins(pluginConfig)

if f, err := cmd.Flags().GetString("config"); err == nil {
Expand All @@ -68,7 +71,7 @@ var runCmd = &cobra.Command{
}
}

// Get hooks signature verification policy
// Get hooks signature verification policy.
hooksConfig.Verification = verificationPolicy()

// The config will be passed to the hooks, and in turn to the plugins that
Expand All @@ -91,11 +94,11 @@ var runCmd = &cobra.Command{
}
}

// Create a new logger from the config
// Create a new logger from the config.
loggerCfg := loggerConfig()
logger := logging.NewLogger(loggerCfg)

// Replace the default logger with the new one from the config
// Replace the default logger with the new one from the config.
hooksConfig.Logger = logger

// This is a notification hook, so we don't care about the result.
Expand All @@ -111,7 +114,7 @@ var runCmd = &cobra.Command{
logger.Error().Err(err).Msg("Failed to run OnNewLogger hooks")
}

// Create and initialize a pool of connections
// Create and initialize a pool of connections.
poolSize, clientConfig := poolConfig()
pool := pool.NewPool(poolSize)

Expand All @@ -136,10 +139,10 @@ var runCmd = &cobra.Command{
"address": clientConfig.Address,
"receiveBufferSize": clientConfig.ReceiveBufferSize,
"receiveChunkSize": clientConfig.ReceiveChunkSize,
"receiveDeadline": clientConfig.ReceiveDeadline,
"sendDeadline": clientConfig.SendDeadline,
"receiveDeadline": clientConfig.ReceiveDeadline.Seconds(),
"sendDeadline": clientConfig.SendDeadline.Seconds(),
"tcpKeepAlive": clientConfig.TCPKeepAlive,
"tcpKeepAlivePeriod": clientConfig.TCPKeepAlivePeriod,
"tcpKeepAlivePeriod": clientConfig.TCPKeepAlivePeriod.Seconds(),
}
_, err := hooksConfig.Run(
context.Background(),
Expand Down Expand Up @@ -178,7 +181,7 @@ var runCmd = &cobra.Command{
logger.Error().Err(err).Msg("Failed to run OnNewPool hooks")
}

// Create a prefork proxy with the pool of clients
// Create a prefork proxy with the pool of clients.
elastic, reuseElasticClients, elasticClientConfig := proxyConfig()
proxy := network.NewProxy(
pool, hooksConfig, elastic, reuseElasticClients, elasticClientConfig, logger)
Expand Down Expand Up @@ -266,7 +269,7 @@ var runCmd = &cobra.Command{
logger.Error().Err(err).Msg("Failed to run OnNewServer hooks")
}

// Shutdown the server gracefully
// Shutdown the server gracefully.
var signals []os.Signal
signals = append(signals,
os.Interrupt,
Expand All @@ -283,7 +286,7 @@ var runCmd = &cobra.Command{
for sig := range signalsCh {
for _, s := range signals {
if sig != s {
// Notify the hooks that the server is shutting down
// Notify the hooks that the server is shutting down.
_, err := hooksConfig.Run(
context.Background(),
map[string]interface{}{"signal": sig.String()},
Expand All @@ -302,7 +305,7 @@ var runCmd = &cobra.Command{
}
}(hooksConfig)

// Run the server
// Run the server.
if err := server.Run(); err != nil {
logger.Error().Err(err).Msg("Failed to start server")
}
Expand Down
3 changes: 0 additions & 3 deletions gatewayd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,3 @@ server:
reusePort: True
tcpKeepAlive: 3s # duration
tcpNoDelay: True

plugins:
verificationPolicy: "passdown"
8 changes: 7 additions & 1 deletion gatewayd_plugins.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Plugin configuration file for GatewayD

plugins:
# Possible values: "passdown" (default), "ignore", "abort" and "remove"
verificationPolicy: "passdown"
# Possible values: "strict" (default) and "loose"
compatibilityPolicy: "strict"

# Plugin name
gatewayd-plugin-test:
# whether to enable or disable the plugin on the next run
Expand All @@ -17,4 +23,4 @@ gatewayd-plugin-test:
- MAGIC_COOKIE_KEY=GATEWAYD_PLUGIN
- MAGIC_COOKIE_VALUE=5712b87aa5d7e9f9e9ab643e6603181c5b796015cb1c09d6f5ada882bf2a1872
# Checksum hash to verify the binary before loading
checksum: 006e19bcfd1951e077746143590934470e8b1d67a3904036603013e150fcb708
checksum: 09bce5ad90a36a6f3ec0804023098519424289f3d68679159d68e0b08ce70c89
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/gatewayd-io/gatewayd
go 1.19

require (
github.com/Masterminds/semver/v3 v3.2.0
github.com/fergusstrange/embedded-postgres v1.19.0
github.com/google/go-cmp v0.5.9
github.com/hashicorp/go-hclog v1.4.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
Expand Down
99 changes: 93 additions & 6 deletions plugin/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package plugin
import (
"context"

semver "github.com/Masterminds/semver/v3"
gerr "github.com/gatewayd-io/gatewayd/errors"
"github.com/gatewayd-io/gatewayd/logging"
pluginV1 "github.com/gatewayd-io/gatewayd/plugin/v1"
Expand All @@ -13,6 +14,8 @@ import (
"google.golang.org/protobuf/types/known/structpb"
)

type CompatPolicy uint

const (
DefaultMinPort uint = 50000
DefaultMaxPort uint = 60000
Expand All @@ -21,19 +24,26 @@ const (
LoggerName string = "plugin"
)

const (
Strict CompatPolicy = iota
Loose
)

type Registry interface {
Add(plugin *Impl) bool
Get(id Identifier) *Impl
List() []Identifier
Exists(name, version, remoteURL string) bool
Remove(id Identifier)
Shutdown()
LoadPlugins(pluginConfig *koanf.Koanf)
RegisterHooks(id Identifier)
}

type RegistryImpl struct {
plugins pool.Pool
hooksConfig *HookConfig
plugins pool.Pool
hooksConfig *HookConfig
CompatPolicy CompatPolicy
}

var _ Registry = &RegistryImpl{}
Expand Down Expand Up @@ -74,6 +84,40 @@ func (reg *RegistryImpl) List() []Identifier {
return plugins
}

// Exists checks if a plugin exists in the registry.
func (reg *RegistryImpl) Exists(name, version, remoteURL string) bool {
for _, plugin := range reg.List() {
if plugin.Name == name && plugin.RemoteURL == remoteURL {
// Parse the supplied version and the version in the registry.
suppliedVer, err := semver.NewVersion(version)
if err != nil {
reg.hooksConfig.Logger.Error().Err(err).Msg(
"Failed to parse supplied plugin version")
return false
}

registryVer, err := semver.NewVersion(plugin.Version)
if err != nil {
reg.hooksConfig.Logger.Error().Err(err).Msg(
"Failed to parse plugin version in registry")
return false
}

// Check if the version of the plugin is less than or equal to
// the version in the registry.
if suppliedVer.LessThan(registryVer) || suppliedVer.Equal(registryVer) {
return true
}

reg.hooksConfig.Logger.Debug().Str("name", name).Str("version", version).Msg(
"Supplied plugin version is greater than the version in registry")
return false
}
}

return false
}

// Remove removes a plugin from the registry.
func (reg *RegistryImpl) Remove(id Identifier) {
reg.plugins.Remove(id)
Expand Down Expand Up @@ -105,6 +149,11 @@ func (reg *RegistryImpl) LoadPlugins(pluginConfig *koanf.Koanf) {

// Add each plugin to the registry.
for priority, name := range plugins {
// Skip the top-level "plugins" key.
if name == "plugins" {
continue
}

reg.hooksConfig.Logger.Debug().Str("name", name).Msg("Loading plugin")
plugin := &Impl{
ID: Identifier{
Expand Down Expand Up @@ -209,24 +258,62 @@ func (reg *RegistryImpl) LoadPlugins(pluginConfig *koanf.Koanf) {
}
}

// Retrieve plugin requirements.
if err := mapstructure.Decode(metadata.Fields["requires"].GetListValue().AsSlice(),
&plugin.Requires); err != nil {
reg.hooksConfig.Logger.Debug().Err(err).Msg("Failed to decode plugin requirements")
}

// Too many requirements or not enough plugins loaded.
if len(plugin.Requires) > reg.plugins.Size() {
reg.hooksConfig.Logger.Debug().Msg(
"The plugin has too many requirements, " +
"and not enough of them exist in the registry, so it won't work properly")
}

// Check if the plugin requirements are met.
for _, req := range plugin.Requires {
if !reg.Exists(req.Name, req.Version, req.RemoteURL) {
reg.hooksConfig.Logger.Debug().Fields(
map[string]interface{}{
"name": plugin.ID.Name,
"requirement": req.Name,
},
).Msg("The plugin requirement is not met, so it won't work properly")
if reg.CompatPolicy == Strict {
reg.hooksConfig.Logger.Debug().Str("name", plugin.ID.Name).Msg(
"Registry is in strict compatibility mode, so the plugin won't be loaded")
plugin.Stop() // Stop the plugin.
continue
} else {
reg.hooksConfig.Logger.Debug().Fields(
map[string]interface{}{
"name": plugin.ID.Name,
"requirement": req.Name,
},
).Msg("Registry is in loose compatibility mode, " +
"so the plugin will be loaded anyway")
}
}
}

plugin.ID.RemoteURL = metadata.Fields["id"].GetStructValue().Fields["remoteUrl"].GetStringValue()
plugin.ID.Version = metadata.Fields["id"].GetStructValue().Fields["version"].GetStringValue()
plugin.Description = metadata.Fields["description"].GetStringValue()
plugin.License = metadata.Fields["license"].GetStringValue()
plugin.ProjectURL = metadata.Fields["projectUrl"].GetStringValue()
if err := mapstructure.Decode(metadata.Fields["requires"].GetListValue().AsSlice(),
&plugin.Requires); err != nil {
reg.hooksConfig.Logger.Debug().Err(err).Msg("Failed to decode plugin requirements")
}
// Retrieve authors.
if err := mapstructure.Decode(metadata.Fields["authors"].GetListValue().AsSlice(),
&plugin.Authors); err != nil {
reg.hooksConfig.Logger.Debug().Err(err).Msg("Failed to decode plugin authors")
}
// Retrieve hooks.
if err := mapstructure.Decode(metadata.Fields["hooks"].GetListValue().AsSlice(),
&plugin.Hooks); err != nil {
reg.hooksConfig.Logger.Debug().Err(err).Msg("Failed to decode plugin hooks")
}

// Retrieve plugin config.
plugin.Config = make(map[string]string)
for key, value := range metadata.Fields["config"].GetStructValue().AsMap() {
if val, ok := value.(string); ok {
Expand Down
28 changes: 14 additions & 14 deletions plugin/v1/plugin.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 5027dec

Please sign in to comment.