Skip to content

Commit 3fef93b

Browse files
committed
Merge pull request #40 from gatewayd-io/plugin-system
Plugin system
2 parents 0d9696c + 19b68e0 commit 3fef93b

21 files changed

+2504
-556
lines changed

buf.gen.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ version: v1
33
# enabled: true
44
plugins:
55
- name: go-grpc
6-
out: gen/proto/go
6+
out: .
77
opt: paths=source_relative
88
- name: go
9-
out: gen/proto/go
9+
out: .
1010
opt: paths=source_relative
1111
# - name: python_betterproto
12-
# out: gen/proto/python_betterproto
12+
# out: .
1313
# - name: java
14-
# out: gen/proto/java
14+
# out: .

cmd/config_parser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ import (
1515
// Global koanf instance. Using "." as the key path delimiter.
1616
var globalConfig = koanf.New(".")
1717

18+
// Plugin koanf instance. Using "." as the key path delimiter.
19+
var pluginConfig = koanf.New(".")
20+
1821
func getPath(path string) string {
1922
ref := globalConfig.String(path)
2023
if globalConfig.Exists(path) && globalConfig.StringMap(ref) != nil {

cmd/run.go

Lines changed: 160 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,66 @@
11
package cmd
22

33
import (
4+
"context"
45
"os"
56
"os/signal"
67
"syscall"
78
"time"
89

10+
gerr "github.com/gatewayd-io/gatewayd/errors"
911
"github.com/gatewayd-io/gatewayd/logging"
1012
"github.com/gatewayd-io/gatewayd/network"
1113
"github.com/gatewayd-io/gatewayd/plugin"
1214
"github.com/gatewayd-io/gatewayd/pool"
13-
"github.com/knadh/koanf"
15+
goplugin "github.com/hashicorp/go-plugin"
1416
"github.com/knadh/koanf/parsers/yaml"
1517
"github.com/knadh/koanf/providers/confmap"
1618
"github.com/knadh/koanf/providers/file"
1719
"github.com/panjf2000/gnet/v2"
20+
"github.com/rs/zerolog"
1821
"github.com/spf13/cobra"
22+
"google.golang.org/protobuf/types/known/structpb"
1923
)
2024

2125
const (
2226
DefaultTCPKeepAlive = 3 * time.Second
2327
)
2428

2529
var (
26-
configFile string
27-
hooksConfig = plugin.NewHookConfig()
30+
globalConfigFile string
31+
pluginConfigFile string
32+
)
33+
34+
var (
35+
hooksConfig = plugin.NewHookConfig()
36+
DefaultLogger = logging.NewLogger(logging.LoggerConfig{Level: zerolog.DebugLevel})
37+
pluginRegistry = plugin.NewRegistry(hooksConfig)
2838
)
2939

3040
// runCmd represents the run command.
3141
var runCmd = &cobra.Command{
3242
Use: "run",
3343
Short: "Run a gatewayd instance",
3444
Run: func(cmd *cobra.Command, args []string) {
45+
// The plugins are loaded and hooks registered
46+
// before the configuration is loaded.
47+
hooksConfig.Logger = DefaultLogger
48+
49+
// Load the plugin configuration file
50+
if f, err := cmd.Flags().GetString("plugin-config"); err == nil {
51+
if err := pluginConfig.Load(file.Provider(f), yaml.Parser()); err != nil {
52+
DefaultLogger.Fatal().Err(err).Msg("Failed to load plugin configuration")
53+
os.Exit(gerr.FailedToLoadPluginConfig)
54+
}
55+
}
56+
57+
// Load plugins and register their hooks
58+
pluginRegistry.LoadPlugins(pluginConfig)
59+
3560
if f, err := cmd.Flags().GetString("config"); err == nil {
3661
if err := globalConfig.Load(file.Provider(f), yaml.Parser()); err != nil {
37-
panic(err)
62+
DefaultLogger.Fatal().Err(err).Msg("Failed to load configuration")
63+
os.Exit(gerr.FailedToLoadGlobalConfig)
3864
}
3965
}
4066

@@ -43,39 +69,47 @@ var runCmd = &cobra.Command{
4369

4470
// The config will be passed to the hooks, and in turn to the plugins that
4571
// register to this hook.
46-
result := hooksConfig.Run(
47-
plugin.OnConfigLoaded,
48-
plugin.Signature{"config": globalConfig.All()},
49-
hooksConfig.Verification)
50-
if result != nil {
51-
var config map[string]interface{}
52-
if cfg, ok := result["config"].(map[string]interface{}); ok {
53-
config = cfg
54-
}
55-
56-
if config != nil {
57-
// Load the config from the map emitted by the hook
58-
var hookEmittedConfig *koanf.Koanf
59-
if err := hookEmittedConfig.Load(confmap.Provider(config, "."), nil); err != nil {
60-
// Since the logger is not yet initialized, we can't log the error.
61-
// So we panic. Same happens in the next if statement.
62-
panic(err)
63-
}
72+
currentGlobalConfig, err := structpb.NewStruct(globalConfig.All())
73+
if err != nil {
74+
DefaultLogger.Error().Err(err).Msg("Failed to convert configuration to structpb")
75+
} else {
76+
updatedGlobalConfig, _ := hooksConfig.Run(
77+
context.Background(),
78+
currentGlobalConfig,
79+
plugin.OnConfigLoaded,
80+
hooksConfig.Verification)
6481

82+
if updatedGlobalConfig != nil && plugin.Verify(updatedGlobalConfig, currentGlobalConfig) {
6583
// Merge the config with the one loaded from the file (in memory).
6684
// The changes won't be persisted to disk.
67-
if err := globalConfig.Merge(hookEmittedConfig); err != nil {
68-
panic(err)
85+
if err := globalConfig.Load(
86+
confmap.Provider(updatedGlobalConfig.AsMap(), "."), nil); err != nil {
87+
DefaultLogger.Fatal().Err(err).Msg("Failed to merge configuration")
6988
}
7089
}
7190
}
7291

7392
// Create a new logger from the config
74-
logger := logging.NewLogger(loggerConfig())
75-
hooksConfig.Logger = logger
93+
loggerCfg := loggerConfig()
94+
logger := logging.NewLogger(loggerCfg)
95+
// TODO: Use https://github.com/dcarbone/zadapters to adapt hclog to zerolog
7696
// This is a notification hook, so we don't care about the result.
77-
hooksConfig.Run(
78-
plugin.OnNewLogger, plugin.Signature{"logger": logger}, hooksConfig.Verification)
97+
data, err := structpb.NewStruct(map[string]interface{}{
98+
"timeFormat": loggerCfg.TimeFormat,
99+
"level": loggerCfg.Level,
100+
"output": loggerCfg.Output,
101+
"noColor": loggerCfg.NoColor,
102+
})
103+
if err != nil {
104+
logger.Error().Err(err).Msg("Failed to convert logger config to structpb")
105+
} else {
106+
// TODO: Use a context with a timeout
107+
_, err := hooksConfig.Run(
108+
context.Background(), data, plugin.OnNewLogger, hooksConfig.Verification)
109+
if err != nil {
110+
logger.Error().Err(err).Msg("Failed to run OnNewLogger hooks")
111+
}
112+
}
79113

80114
// Create and initialize a pool of connections
81115
pool := pool.NewPool()
@@ -90,15 +124,26 @@ var runCmd = &cobra.Command{
90124
logger,
91125
)
92126

93-
hooksConfig.Run(
94-
plugin.OnNewClient,
95-
plugin.Signature{
96-
"client": client,
97-
},
98-
hooksConfig.Verification,
99-
)
100-
101127
if client != nil {
128+
clientCfg, err := structpb.NewStruct(map[string]interface{}{
129+
"id": client.ID,
130+
"network": clientConfig.Network,
131+
"address": clientConfig.Address,
132+
"receiveBufferSize": clientConfig.ReceiveBufferSize,
133+
})
134+
if err != nil {
135+
logger.Error().Err(err).Msg("Failed to convert client config to structpb")
136+
} else {
137+
_, err := hooksConfig.Run(
138+
context.Background(),
139+
clientCfg,
140+
plugin.OnNewClient,
141+
hooksConfig.Verification)
142+
if err != nil {
143+
logger.Error().Err(err).Msg("Failed to run OnNewClient hooks")
144+
}
145+
}
146+
102147
pool.Put(client.ID, client)
103148
}
104149
}
@@ -113,15 +158,38 @@ var runCmd = &cobra.Command{
113158
os.Exit(1)
114159
}
115160

116-
hooksConfig.Run(
117-
plugin.OnNewPool, plugin.Signature{"pool": pool}, hooksConfig.Verification)
161+
poolCfg, err := structpb.NewStruct(map[string]interface{}{
162+
"size": poolSize,
163+
})
164+
if err != nil {
165+
logger.Error().Err(err).Msg("Failed to convert pool config to structpb")
166+
} else {
167+
_, err := hooksConfig.Run(
168+
context.Background(), poolCfg, plugin.OnNewPool, hooksConfig.Verification)
169+
if err != nil {
170+
logger.Error().Err(err).Msg("Failed to run OnNewPool hooks")
171+
}
172+
}
118173

119174
// Create a prefork proxy with the pool of clients
120175
elastic, reuseElasticClients, elasticClientConfig := proxyConfig()
121176
proxy := network.NewProxy(
122177
pool, hooksConfig, elastic, reuseElasticClients, elasticClientConfig, logger)
123-
hooksConfig.Run(
124-
plugin.OnNewProxy, plugin.Signature{"proxy": proxy}, hooksConfig.Verification)
178+
179+
proxyCfg, err := structpb.NewStruct(map[string]interface{}{
180+
"elastic": elastic,
181+
"reuseElasticClients": reuseElasticClients,
182+
"clientConfig": elasticClientConfig,
183+
})
184+
if err != nil {
185+
logger.Error().Err(err).Msg("Failed to convert proxy config to structpb")
186+
} else {
187+
_, err := hooksConfig.Run(
188+
context.Background(), proxyCfg, plugin.OnNewProxy, hooksConfig.Verification)
189+
if err != nil {
190+
logger.Error().Err(err).Msg("Failed to run OnNewProxy hooks")
191+
}
192+
}
125193

126194
// Create a server
127195
serverConfig := serverConfig()
@@ -166,9 +234,35 @@ var runCmd = &cobra.Command{
166234
logger,
167235
hooksConfig,
168236
)
169-
hooksConfig.Run(
170-
plugin.OnNewServer, plugin.Signature{"server": server}, hooksConfig.Verification)
171237

238+
serverCfg, err := structpb.NewStruct(map[string]interface{}{
239+
"network": serverConfig.Network,
240+
"address": serverConfig.Address,
241+
"softLimit": serverConfig.SoftLimit,
242+
"hardLimit": serverConfig.HardLimit,
243+
"tickInterval": serverConfig.TickInterval,
244+
"multiCore": serverConfig.MultiCore,
245+
"lockOSThread": serverConfig.LockOSThread,
246+
"enableTicker": serverConfig.EnableTicker,
247+
"loadBalancer": serverConfig.LoadBalancer,
248+
"readBufferCap": serverConfig.ReadBufferCap,
249+
"writeBufferCap": serverConfig.WriteBufferCap,
250+
"socketRecvBuffer": serverConfig.SocketRecvBuffer,
251+
"socketSendBuffer": serverConfig.SocketSendBuffer,
252+
"reuseAddress": serverConfig.ReuseAddress,
253+
"reusePort": serverConfig.ReusePort,
254+
"tcpKeepAlive": serverConfig.TCPKeepAlive,
255+
"tcpNoDelay": serverConfig.TCPNoDelay,
256+
})
257+
if err != nil {
258+
logger.Error().Err(err).Msg("Failed to convert server config to structpb")
259+
} else {
260+
_, err := hooksConfig.Run(
261+
context.Background(), serverCfg, plugin.OnNewServer, hooksConfig.Verification)
262+
if err != nil {
263+
logger.Error().Err(err).Msg("Failed to run OnNewServer hooks")
264+
}
265+
}
172266
// Shutdown the server gracefully
173267
var signals []os.Signal
174268
signals = append(signals,
@@ -187,10 +281,25 @@ var runCmd = &cobra.Command{
187281
for _, s := range signals {
188282
if sig != s {
189283
// Notify the hooks that the server is shutting down
190-
hooksConfig.Run(
191-
plugin.OnSignal, plugin.Signature{"signal": sig}, hooksConfig.Verification)
284+
signalCfg, err := structpb.NewStruct(map[string]interface{}{"signal": sig})
285+
if err != nil {
286+
logger.Error().Err(err).Msg(
287+
"Failed to convert signal config to structpb")
288+
} else {
289+
_, err := hooksConfig.Run(
290+
context.Background(),
291+
signalCfg,
292+
plugin.OnSignal,
293+
hooksConfig.Verification,
294+
)
295+
if err != nil {
296+
logger.Error().Err(err).Msg("Failed to run OnSignal hooks")
297+
}
298+
}
192299

193300
server.Shutdown()
301+
pluginRegistry.Shutdown()
302+
goplugin.CleanupClients()
194303
os.Exit(0)
195304
}
196305
}
@@ -208,5 +317,11 @@ func init() {
208317
rootCmd.AddCommand(runCmd)
209318

210319
runCmd.PersistentFlags().StringVarP(
211-
&configFile, "config", "c", "./gatewayd.yaml", "config file (default is ./gatewayd.yaml)")
320+
&globalConfigFile,
321+
"config", "c", "./gatewayd.yaml",
322+
"config file (default is ./gatewayd.yaml)")
323+
runCmd.PersistentFlags().StringVarP(
324+
&pluginConfigFile,
325+
"plugin-config", "p", "./gatewayd_plugins.yaml",
326+
"plugin config file (default is ./gatewayd_plugins.yaml)")
212327
}
Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package network
1+
package errors
22

33
import "errors"
44

@@ -7,4 +7,12 @@ var (
77
ErrNetworkNotSupported = errors.New("network is not supported")
88
ErrClientNotConnected = errors.New("client is not connected")
99
ErrPoolExhausted = errors.New("pool is exhausted")
10+
11+
ErrPluginNotFound = errors.New("plugin not found")
12+
ErrPluginNotReady = errors.New("plugin is not ready")
13+
)
14+
15+
const (
16+
FailedToLoadPluginConfig = 1
17+
FailedToLoadGlobalConfig = 2
1018
)

gatewayd_plugins.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
gatewayd-plugin-test:
2+
enabled: True
3+
localPath: ../gatewayd-plugin-test/gatewayd-plugin-test
4+
checksum: 9c12267332a609ed7c739098a0aa7614f580ba174f35f611fc1cc53de5647bff

go.mod

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,17 @@ module github.com/gatewayd-io/gatewayd
33
go 1.19
44

55
require (
6-
github.com/Masterminds/semver/v3 v3.2.0
76
github.com/fergusstrange/embedded-postgres v1.19.0
8-
github.com/gatewayd-io/gatewayd-plugin-test v0.0.0-00010101000000-000000000000
97
github.com/google/go-cmp v0.5.9
108
github.com/hashicorp/go-plugin v1.4.8
119
github.com/knadh/koanf v1.4.4
10+
github.com/mitchellh/mapstructure v1.5.0
1211
github.com/panjf2000/gnet/v2 v2.2.0
1312
github.com/rs/zerolog v1.28.0
1413
github.com/spf13/cobra v1.6.1
1514
github.com/stretchr/testify v1.8.1
1615
google.golang.org/grpc v1.51.0
16+
google.golang.org/protobuf v1.28.1
1717
)
1818

1919
require (
@@ -29,7 +29,6 @@ require (
2929
github.com/mattn/go-isatty v0.0.16 // indirect
3030
github.com/mitchellh/copystructure v1.2.0 // indirect
3131
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
32-
github.com/mitchellh/mapstructure v1.5.0 // indirect
3332
github.com/mitchellh/reflectwalk v1.0.2 // indirect
3433
github.com/oklog/run v1.1.0 // indirect
3534
github.com/pmezard/go-difflib v1.0.0 // indirect
@@ -42,7 +41,6 @@ require (
4241
golang.org/x/sys v0.3.0 // indirect
4342
golang.org/x/text v0.5.0 // indirect
4443
google.golang.org/genproto v0.0.0-20221207170731-23e4bf6bdc37 // indirect
45-
google.golang.org/protobuf v1.28.1 // indirect
4644
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
4745
gopkg.in/yaml.v3 v3.0.1 // indirect
4846
)

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
22
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
33
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
44
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
5-
github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g=
6-
github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ=
75
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
86
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
97
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=

0 commit comments

Comments
 (0)