diff --git a/context.go b/context.go index ca2f44ce..cfb183a2 100644 --- a/context.go +++ b/context.go @@ -10,12 +10,30 @@ import ( "github.com/mholt/certmagic" ) +// Context is a type which defines the lifetime of modules that +// are loaded and provides access to the parent configuration +// that spawned the modules which are loaded. It should be used +// with care and only wrapped with derivation functions from +// the standard context package if you don't need the Caddy +// specific features. These contexts are cancelled when the +// lifetime of the modules loaded from it are over. +// +// Use NewContext() to get a valid value (but most modules will +// not actually need to do this). type Context struct { context.Context moduleInstances map[string][]interface{} cfg *Config } +// NewContext provides a new context derived from the given +// context ctx. Normally, you will not need to call this +// function unless you are loading modules which have a +// different lifespan than the ones for the context the +// module was provisioned with. Be sure to call the cancel +// func when the context is to be cleaned up so that +// modules which are loaded will be properly unloaded. +// See standard library context package's documentation. func NewContext(ctx Context) (Context, context.CancelFunc) { newCtx := Context{moduleInstances: make(map[string][]interface{}), cfg: ctx.cfg} c, cancel := context.WithCancel(ctx.Context) @@ -36,6 +54,14 @@ func NewContext(ctx Context) (Context, context.CancelFunc) { return newCtx, wrappedCancel } +// LoadModule decodes rawMsg into a new instance of mod and +// returns the value. If mod.New() does not return a pointer +// value, it is converted to one so that it is unmarshaled +// into the underlying concrete type. If mod.New is nil, an +// error is returned. If the module implements Validator or +// Provisioner interfaces, those methods are invoked to +// ensure the module is fully configured and valid before +// being used. func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) { modulesMu.Lock() mod, ok := modules[name] @@ -74,7 +100,7 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, } if validator, ok := val.(Validator); ok { - err := validator.Validate(ctx) + err := validator.Validate() if err != nil { if cleanerUpper, ok := val.(CleanerUpper); ok { err2 := cleanerUpper.Cleanup() @@ -91,6 +117,17 @@ func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, return val, nil } +// LoadModuleInline loads a module from a JSON raw message which decodes +// to a map[string]interface{}, where one of the keys is moduleNameKey +// and the corresponding value is the module name as a string, which +// can be found in the given scope. +// +// This allows modules to be decoded into their concrete types and +// used when their names cannot be the unique key in a map, such as +// when there are multiple instances in the map or it appears in an +// array (where there are no custom keys). In other words, the key +// containing the module name is treated special/separate from all +// the other keys. func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) { moduleName, err := getModuleNameInline(moduleNameKey, raw) if err != nil { diff --git a/modules.go b/modules.go index bba6b93a..afe4b385 100644 --- a/modules.go +++ b/modules.go @@ -136,23 +136,32 @@ func getModuleNameInline(moduleNameKey string, raw json.RawMessage) (string, err return moduleName, nil } -// Validator is implemented by modules which can verify that their -// configurations are valid. This method will be called after New() -// instantiations of modules (if implemented). Validation should -// always be fast (imperceptible running time) and an error should -// be returned only if the value's configuration is invalid. -type Validator interface { - Validate(Context) error -} - // Provisioner is implemented by modules which may need to perform // some additional "setup" steps immediately after being loaded. -// This method will be called after Validate() (if implemented). +// Provisioning should be fast (imperceptible running time). If +// any side-effects result in the execution of this function (e.g. +// creating global state, any other allocations which require +// garbage collection, opening files, starting goroutines etc.), +// be sure to clean up properly by implementing the CleanerUpper +// interface to avoid leaking resources. type Provisioner interface { Provision(Context) error } -// TODO: different name... +// Validator is implemented by modules which can verify that their +// configurations are valid. This method will be called after +// Provision() (if implemented). Validation should always be fast +// (imperceptible running time) and an error should be returned only +// if the value's configuration is invalid. +type Validator interface { + Validate() error +} + +// CleanerUpper is implemented by modules which may have side-effects +// such as opened files, spawned goroutines, or allocated some sort +// of non-local state when they were provisioned. This method should +// deallocate/cleanup those resources to prevent memory leaks. Cleanup +// should be fast and efficient. type CleanerUpper interface { Cleanup() error }