From 1f0c061ce30f218e63fcc17e0fdfc8b90d754ba5 Mon Sep 17 00:00:00 2001 From: Matthew Holt Date: Thu, 16 May 2019 16:05:38 -0600 Subject: Architectural shift to using context for config and module state --- context.go | 126 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 context.go (limited to 'context.go') diff --git a/context.go b/context.go new file mode 100644 index 0000000..ca2f44c --- /dev/null +++ b/context.go @@ -0,0 +1,126 @@ +package caddy2 + +import ( + "context" + "encoding/json" + "fmt" + "log" + "reflect" + + "github.com/mholt/certmagic" +) + +type Context struct { + context.Context + moduleInstances map[string][]interface{} + cfg *Config +} + +func NewContext(ctx Context) (Context, context.CancelFunc) { + newCtx := Context{moduleInstances: make(map[string][]interface{}), cfg: ctx.cfg} + c, cancel := context.WithCancel(ctx.Context) + wrappedCancel := func() { + cancel() + for modName, modInstances := range newCtx.moduleInstances { + for _, inst := range modInstances { + if cu, ok := inst.(CleanerUpper); ok { + err := cu.Cleanup() + if err != nil { + log.Printf("[ERROR] %s (%p): cleanup: %v", modName, inst, err) + } + } + } + } + } + newCtx.Context = c + return newCtx, wrappedCancel +} + +func (ctx Context) LoadModule(name string, rawMsg json.RawMessage) (interface{}, error) { + modulesMu.Lock() + mod, ok := modules[name] + modulesMu.Unlock() + if !ok { + return nil, fmt.Errorf("unknown module: %s", name) + } + + if mod.New == nil { + return nil, fmt.Errorf("module '%s' has no constructor", mod.Name) + } + + val, err := mod.New() + if err != nil { + return nil, fmt.Errorf("initializing module '%s': %v", mod.Name, err) + } + + // value must be a pointer for unmarshaling into concrete type + if rv := reflect.ValueOf(val); rv.Kind() != reflect.Ptr { + val = reflect.New(rv.Type()).Elem().Addr().Interface() + } + + // fill in its config only if there is a config to fill in + if len(rawMsg) > 0 { + err = json.Unmarshal(rawMsg, &val) + if err != nil { + return nil, fmt.Errorf("decoding module config: %s: %v", mod.Name, err) + } + } + + if prov, ok := val.(Provisioner); ok { + err := prov.Provision(ctx) + if err != nil { + return nil, fmt.Errorf("provision %s: %v", mod.Name, err) + } + } + + if validator, ok := val.(Validator); ok { + err := validator.Validate(ctx) + if err != nil { + if cleanerUpper, ok := val.(CleanerUpper); ok { + err2 := cleanerUpper.Cleanup() + if err2 != nil { + err = fmt.Errorf("%v; additionally, cleanup: %v", err, err2) + } + return nil, fmt.Errorf("%s: invalid configuration: %v", mod.Name, err) + } + } + } + + ctx.moduleInstances[name] = append(ctx.moduleInstances[name], val) + + return val, nil +} + +func (ctx Context) LoadModuleInline(moduleNameKey, moduleScope string, raw json.RawMessage) (interface{}, error) { + moduleName, err := getModuleNameInline(moduleNameKey, raw) + if err != nil { + return nil, err + } + + val, err := ctx.LoadModule(moduleScope+"."+moduleName, raw) + if err != nil { + return nil, fmt.Errorf("loading module '%s': %v", moduleName, err) + } + + return val, nil +} + +// App returns the configured app named name. If no app with +// that name is currently configured, a new empty one will be +// instantiated. (The app module must still be registered.) +func (ctx Context) App(name string) (interface{}, error) { + if app, ok := ctx.cfg.apps[name]; ok { + return app, nil + } + modVal, err := ctx.LoadModule(name, nil) + if err != nil { + return nil, fmt.Errorf("instantiating new module %s: %v", name, err) + } + ctx.cfg.apps[name] = modVal.(App) + return modVal, nil +} + +// Storage returns the configured Caddy storage implementation. +func (ctx Context) Storage() certmagic.Storage { + return ctx.cfg.storage +} -- cgit v1.2.3