-
Notifications
You must be signed in to change notification settings - Fork 3
/
plugins.go
470 lines (413 loc) · 14.4 KB
/
plugins.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
// Copyright 2015 Light Code Labs, LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package casket
import (
"fmt"
"log"
"net"
"sort"
"sync"
"github.com/tmpim/casket/casketfile"
)
// These are all the registered plugins.
var (
// serverTypes is a map of registered server types.
serverTypes = make(map[string]ServerType)
// plugins is a map of server type to map of plugin name to
// Plugin. These are the "general" plugins that may or may
// not be associated with a specific server type. If it's
// applicable to multiple server types or the server type is
// irrelevant, the key is empty string (""). But all plugins
// must have a name.
plugins = make(map[string]map[string]Plugin)
// eventHooks is a map of hook name to Hook. All hooks plugins
// must have a name.
eventHooks = &sync.Map{}
// parsingCallbacks maps server type to map of directive
// to list of callback functions. These aren't really
// plugins on their own, but are often registered from
// plugins.
parsingCallbacks = make(map[string]map[string][]ParsingCallback)
// casketfileLoaders is the list of all Casketfile loaders
// in registration order.
casketfileLoaders []casketfileLoader
)
// DescribePlugins returns a string describing the registered plugins.
func DescribePlugins() string {
pl := ListPlugins()
str := "Server types:\n"
for _, name := range pl["server_types"] {
str += " " + name + "\n"
}
str += "\nCasketfile loaders:\n"
for _, name := range pl["casketfile_loaders"] {
str += " " + name + "\n"
}
if len(pl["event_hooks"]) > 0 {
str += "\nEvent hook plugins:\n"
for _, name := range pl["event_hooks"] {
str += " hook." + name + "\n"
}
}
if len(pl["clustering"]) > 0 {
str += "\nClustering plugins:\n"
for _, name := range pl["clustering"] {
str += " " + name + "\n"
}
}
str += "\nOther plugins:\n"
for _, name := range pl["others"] {
str += " " + name + "\n"
}
return str
}
// ListPlugins makes a list of the registered plugins,
// keyed by plugin type.
func ListPlugins() map[string][]string {
p := make(map[string][]string)
// server type plugins
for name := range serverTypes {
p["server_types"] = append(p["server_types"], name)
}
// casketfile loaders in registration order
for _, loader := range casketfileLoaders {
p["casketfile_loaders"] = append(p["casketfile_loaders"], loader.name)
}
if defaultCasketfileLoader.name != "" {
p["casketfile_loaders"] = append(p["casketfile_loaders"], defaultCasketfileLoader.name)
}
// List the event hook plugins
eventHooks.Range(func(k, _ interface{}) bool {
p["event_hooks"] = append(p["event_hooks"], k.(string))
return true
})
// alphabetize the rest of the plugins
var others []string
for stype, stypePlugins := range plugins {
for name := range stypePlugins {
var s string
if stype != "" {
s = stype + "."
}
s += name
others = append(others, s)
}
}
sort.Strings(others)
for _, name := range others {
p["others"] = append(p["others"], name)
}
return p
}
// ValidDirectives returns the list of all directives that are
// recognized for the server type serverType. However, not all
// directives may be installed. This makes it possible to give
// more helpful error messages, like "did you mean ..." or
// "maybe you need to plug in ...".
func ValidDirectives(serverType string) []string {
stype, err := getServerType(serverType)
if err != nil {
return nil
}
return stype.Directives()
}
// ServerListener pairs a server to its listener and/or packetconn.
type ServerListener struct {
server Server
listener net.Listener
packet net.PacketConn
}
// LocalAddr returns the local network address of the packetconn. It returns
// nil when it is not set.
func (s ServerListener) LocalAddr() net.Addr {
if s.packet == nil {
return nil
}
return s.packet.LocalAddr()
}
// Addr returns the listener's network address. It returns nil when it is
// not set.
func (s ServerListener) Addr() net.Addr {
if s.listener == nil {
return nil
}
return s.listener.Addr()
}
// Context is a type which carries a server type through
// the load and setup phase; it maintains the state
// between loading the Casketfile, then executing its
// directives, then making the servers for Casket to
// manage. Typically, such state involves configuration
// structs, etc.
type Context interface {
// Called after the Casketfile is parsed into server
// blocks but before the directives are executed,
// this method gives you an opportunity to inspect
// the server blocks and prepare for the execution
// of directives. Return the server blocks (which
// you may modify, if desired) and an error, if any.
// The first argument is the name or path to the
// configuration file (Casketfile).
//
// This function can be a no-op and simply return its
// input if there is nothing to do here.
InspectServerBlocks(string, []casketfile.ServerBlock) ([]casketfile.ServerBlock, error)
// This is what Casket calls to make server instances.
// By this time, all directives have been executed and,
// presumably, the context has enough state to produce
// server instances for Casket to start.
MakeServers() ([]Server, error)
}
// RegisterServerType registers a server type srv by its
// name, typeName.
func RegisterServerType(typeName string, srv ServerType) {
if _, ok := serverTypes[typeName]; ok {
panic("server type already registered")
}
serverTypes[typeName] = srv
}
// ServerType contains information about a server type.
type ServerType struct {
// Function that returns the list of directives, in
// execution order, that are valid for this server
// type. Directives should be one word if possible
// and lower-cased.
Directives func() []string
// DefaultInput returns a default config input if none
// is otherwise loaded. This is optional, but highly
// recommended, otherwise a blank Casketfile will be
// used.
DefaultInput func() Input
// The function that produces a new server type context.
// This will be called when a new Casketfile is being
// loaded, parsed, and executed independently of any
// startup phases before this one. It's a way to keep
// each set of server instances separate and to reduce
// the amount of global state you need.
NewContext func(inst *Instance) Context
}
// Plugin is a type which holds information about a plugin.
type Plugin struct {
// ServerType is the type of server this plugin is for.
// Can be empty if not applicable, or if the plugin
// can associate with any server type.
ServerType string
// Action is the plugin's setup function, if associated
// with a directive in the Casketfile.
Action SetupFunc
}
// RegisterPlugin plugs in plugin. All plugins should register
// themselves, even if they do not perform an action associated
// with a directive. It is important for the process to know
// which plugins are available.
//
// The plugin MUST have a name: lower case and one word.
// If this plugin has an action, it must be the name of
// the directive that invokes it. A name is always required
// and must be unique for the server type.
func RegisterPlugin(name string, plugin Plugin) {
if name == "" {
panic("plugin must have a name")
}
if _, ok := plugins[plugin.ServerType]; !ok {
plugins[plugin.ServerType] = make(map[string]Plugin)
}
if _, dup := plugins[plugin.ServerType][name]; dup {
panic("plugin named " + name + " already registered for server type " + plugin.ServerType)
}
plugins[plugin.ServerType][name] = plugin
}
// EventName represents the name of an event used with event hooks.
type EventName string
// Define names for the various events
const (
StartupEvent EventName = "startup"
ShutdownEvent = "shutdown"
CertRenewEvent = "certrenew"
InstanceStartupEvent = "instancestartup"
InstanceRestartEvent = "instancerestart"
)
// EventHook is a type which holds information about a startup hook plugin.
type EventHook func(eventType EventName, eventInfo interface{}) error
// RegisterEventHook plugs in hook. All the hooks should register themselves
// and they must have a name.
func RegisterEventHook(name string, hook EventHook) {
if name == "" {
panic("event hook must have a name")
}
_, dup := eventHooks.LoadOrStore(name, hook)
if dup {
panic("hook named " + name + " already registered")
}
}
// EmitEvent executes the different hooks passing the EventType as an
// argument. This is a blocking function. Hook developers should
// use 'go' keyword if they don't want to block Casket.
func EmitEvent(event EventName, info interface{}) {
eventHooks.Range(func(k, v interface{}) bool {
err := v.(EventHook)(event, info)
if err != nil {
log.Printf("error on '%s' hook: %v", k.(string), err)
}
return true
})
}
// cloneEventHooks return a clone of the event hooks *sync.Map
func cloneEventHooks() *sync.Map {
c := &sync.Map{}
eventHooks.Range(func(k, v interface{}) bool {
c.Store(k, v)
return true
})
return c
}
// purgeEventHooks purges all event hooks from the map
func purgeEventHooks() {
eventHooks.Range(func(k, _ interface{}) bool {
eventHooks.Delete(k)
return true
})
}
// restoreEventHooks restores eventHooks with a provided *sync.Map
func restoreEventHooks(m *sync.Map) {
// Purge old event hooks
purgeEventHooks()
// Restore event hooks
m.Range(func(k, v interface{}) bool {
eventHooks.Store(k, v)
return true
})
}
// ParsingCallback is a function that is called after
// a directive's setup functions have been executed
// for all the server blocks.
type ParsingCallback func(Context) error
// RegisterParsingCallback registers callback to be called after
// executing the directive afterDir for server type serverType.
func RegisterParsingCallback(serverType, afterDir string, callback ParsingCallback) {
if _, ok := parsingCallbacks[serverType]; !ok {
parsingCallbacks[serverType] = make(map[string][]ParsingCallback)
}
parsingCallbacks[serverType][afterDir] = append(parsingCallbacks[serverType][afterDir], callback)
}
// SetupFunc is used to set up a plugin, or in other words,
// execute a directive. It will be called once per key for
// each server block it appears in.
type SetupFunc func(c *Controller) error
// DirectiveAction gets the action for directive dir of
// server type serverType.
func DirectiveAction(serverType, dir string) (SetupFunc, error) {
if stypePlugins, ok := plugins[serverType]; ok {
if plugin, ok := stypePlugins[dir]; ok {
return plugin.Action, nil
}
}
if genericPlugins, ok := plugins[""]; ok {
if plugin, ok := genericPlugins[dir]; ok {
return plugin.Action, nil
}
}
return nil, fmt.Errorf("no action found for directive '%s' with server type '%s' (missing a plugin?)",
dir, serverType)
}
// Loader is a type that can load a Casketfile.
// It is passed the name of the server type.
// It returns an error only if something went
// wrong, not simply if there is no Casketfile
// for this loader to load.
//
// A Loader should only load the Casketfile if
// a certain condition or requirement is met,
// as returning a non-nil Input value along with
// another Loader will result in an error.
// In other words, loading the Casketfile must
// be deliberate & deterministic, not haphazard.
//
// The exception is the default Casketfile loader,
// which will be called only if no other Casketfile
// loaders return a non-nil Input. The default
// loader may always return an Input value.
type Loader interface {
Load(serverType string) (Input, error)
}
// LoaderFunc is a convenience type similar to http.HandlerFunc
// that allows you to use a plain function as a Load() method.
type LoaderFunc func(serverType string) (Input, error)
// Load loads a Casketfile.
func (lf LoaderFunc) Load(serverType string) (Input, error) {
return lf(serverType)
}
// RegisterCasketfileLoader registers loader named name.
func RegisterCasketfileLoader(name string, loader Loader) {
casketfileLoaders = append(casketfileLoaders, casketfileLoader{name: name, loader: loader})
}
// SetDefaultCasketfileLoader registers loader by name
// as the default Casketfile loader if no others produce
// a Casketfile. If another Casketfile loader has already
// been set as the default, this replaces it.
//
// Do not call RegisterCasketfileLoader on the same
// loader; that would be redundant.
func SetDefaultCasketfileLoader(name string, loader Loader) {
defaultCasketfileLoader = casketfileLoader{name: name, loader: loader}
}
// loadCasketfileInput iterates the registered Casketfile loaders
// and, if needed, calls the default loader, to load a Casketfile.
// It is an error if any of the loaders return an error or if
// more than one loader returns a Casketfile.
func loadCasketfileInput(serverType string) (Input, error) {
var loadedBy string
var casketfileToUse Input
for _, l := range casketfileLoaders {
cdyfile, err := l.loader.Load(serverType)
if err != nil {
return nil, fmt.Errorf("loading Casketfile via %s: %v", l.name, err)
}
if cdyfile != nil {
if casketfileToUse != nil {
return nil, fmt.Errorf("Casketfile loaded multiple times; first by %s, then by %s", loadedBy, l.name)
}
loaderUsed = l
casketfileToUse = cdyfile
loadedBy = l.name
}
}
if casketfileToUse == nil && defaultCasketfileLoader.loader != nil {
cdyfile, err := defaultCasketfileLoader.loader.Load(serverType)
if err != nil {
return nil, err
}
if cdyfile != nil {
loaderUsed = defaultCasketfileLoader
casketfileToUse = cdyfile
}
}
return casketfileToUse, nil
}
// OnProcessExit is a list of functions to run when the process
// exits -- they are ONLY for cleanup and should not block,
// return errors, or do anything fancy. They will be run with
// every signal, even if "shutdown callbacks" are not executed.
// This variable must only be modified in the main goroutine
// from init() functions.
var OnProcessExit []func()
// casketfileLoader pairs the name of a loader to the loader.
type casketfileLoader struct {
name string
loader Loader
}
var (
defaultCasketfileLoader casketfileLoader // the default loader if all else fail
loaderUsed casketfileLoader // the loader that was used (relevant for reloads)
)