summaryrefslogtreecommitdiff
path: root/context.go
blob: cfb183a22f7bd3c6a99c98c739303966ba0af81e (plain)
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
package caddy2

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"reflect"

	"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)
	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
}

// 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]
	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()
		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
}

// 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 {
		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
}