aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndreas Kohn <[email protected]>2024-06-06 22:36:06 +0200
committerGitHub <[email protected]>2024-06-06 14:36:06 -0600
commita10117f8bdbfd72fe585b7bb0c4b43ad8f6908bc (patch)
treec112602feba5629043290cabd4b6d10fc3106fd5
parent101d3e740783581110340a68f0b0cbe5f1ab6dbb (diff)
downloadcaddy-a10117f8bdbfd72fe585b7bb0c4b43ad8f6908bc.tar.gz
caddy-a10117f8bdbfd72fe585b7bb0c4b43ad8f6908bc.zip
core: Split `run` into a public `ProvisionContext` and a private method (#6378)
* Split `run` into a public `BuildContext` and a private part `BuildContext` can be used to set up a caddy context from a config, but not start any listeners or active components: The returned context has the configured apps provisioned, but otherwise is inert. This is EXPERIMENTAL: Minimally it's missing documentation and the example for how this can be used to run unit tests. * Use the config from the context The config passed into `BuildContext` can be nil, in which case `BuildContext` will just make one up that works. In either case that will end up in the finished context. * Rename `BuildContext` to `ProvisionContext` to better match the function * Hide the `replaceAdminServer` parts The admin server is a global thing, and in the envisioned use case for `ProvisionContext` shouldn't actually exist. Hide this detail in a private `provisionContext` instead, and only expose it publicly with `replaceAdminServer` set to `false`. This should reduce foot-shooting potential further; in addition the documentation comment now clearly spells out that the exact interface and implementation details of `ProvisionContext` are experimental and subject to change.
-rw-r--r--caddy.go105
1 files changed, 62 insertions, 43 deletions
diff --git a/caddy.go b/caddy.go
index 27acabb12..7dd989c9e 100644
--- a/caddy.go
+++ b/caddy.go
@@ -397,6 +397,58 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
// will want to use Run instead, which also
// updates the config's raw state.
func run(newCfg *Config, start bool) (Context, error) {
+ ctx, err := provisionContext(newCfg, start)
+ if err != nil {
+ return ctx, err
+ }
+
+ if !start {
+ return ctx, nil
+ }
+
+ // Provision any admin routers which may need to access
+ // some of the other apps at runtime
+ err = ctx.cfg.Admin.provisionAdminRouters(ctx)
+ if err != nil {
+ return ctx, err
+ }
+
+ // Start
+ err = func() error {
+ started := make([]string, 0, len(ctx.cfg.apps))
+ for name, a := range ctx.cfg.apps {
+ err := a.Start()
+ if err != nil {
+ // an app failed to start, so we need to stop
+ // all other apps that were already started
+ for _, otherAppName := range started {
+ err2 := ctx.cfg.apps[otherAppName].Stop()
+ if err2 != nil {
+ err = fmt.Errorf("%v; additionally, aborting app %s: %v",
+ err, otherAppName, err2)
+ }
+ }
+ return fmt.Errorf("%s app module: start: %v", name, err)
+ }
+ started = append(started, name)
+ }
+ return nil
+ }()
+ if err != nil {
+ return ctx, err
+ }
+
+ // now that the user's config is running, finish setting up anything else,
+ // such as remote admin endpoint, config loader, etc.
+ return ctx, finishSettingUp(ctx, ctx.cfg)
+}
+
+// provisionContext creates a new context from the given configuration and provisions
+// storage and apps.
+// If `newCfg` is nil a new empty configuration will be created.
+// If `replaceAdminServer` is true any currently active admin server will be replaced
+// with a new admin server based on the provided configuration.
+func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error) {
// because we will need to roll back any state
// modifications if this function errors, we
// keep a single error value and scope all
@@ -444,7 +496,7 @@ func run(newCfg *Config, start bool) (Context, error) {
}
// start the admin endpoint (and stop any prior one)
- if start {
+ if replaceAdminServer {
err = replaceLocalAdminServer(newCfg)
if err != nil {
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
@@ -491,49 +543,16 @@ func run(newCfg *Config, start bool) (Context, error) {
}
return nil
}()
- if err != nil {
- return ctx, err
- }
-
- if !start {
- return ctx, nil
- }
-
- // Provision any admin routers which may need to access
- // some of the other apps at runtime
- err = newCfg.Admin.provisionAdminRouters(ctx)
- if err != nil {
- return ctx, err
- }
-
- // Start
- err = func() error {
- started := make([]string, 0, len(newCfg.apps))
- for name, a := range newCfg.apps {
- err := a.Start()
- if err != nil {
- // an app failed to start, so we need to stop
- // all other apps that were already started
- for _, otherAppName := range started {
- err2 := newCfg.apps[otherAppName].Stop()
- if err2 != nil {
- err = fmt.Errorf("%v; additionally, aborting app %s: %v",
- err, otherAppName, err2)
- }
- }
- return fmt.Errorf("%s app module: start: %v", name, err)
- }
- started = append(started, name)
- }
- return nil
- }()
- if err != nil {
- return ctx, err
- }
+ return ctx, err
+}
- // now that the user's config is running, finish setting up anything else,
- // such as remote admin endpoint, config loader, etc.
- return ctx, finishSettingUp(ctx, newCfg)
+// ProvisionContext creates a new context from the configuration and provisions storage
+// and app modules.
+// The function is intended for testing and advanced use cases only, typically `Run` should be
+// use to ensure a fully functional caddy instance.
+// EXPERIMENTAL: While this is public the interface and implementation details of this function may change.
+func ProvisionContext(newCfg *Config) (Context, error) {
+ return provisionContext(newCfg, false)
}
// finishSettingUp should be run after all apps have successfully started.