diff options
author | Andreas Kohn <[email protected]> | 2024-06-06 22:36:06 +0200 |
---|---|---|
committer | GitHub <[email protected]> | 2024-06-06 14:36:06 -0600 |
commit | a10117f8bdbfd72fe585b7bb0c4b43ad8f6908bc (patch) | |
tree | c112602feba5629043290cabd4b6d10fc3106fd5 | |
parent | 101d3e740783581110340a68f0b0cbe5f1ab6dbb (diff) | |
download | caddy-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.go | 105 |
1 files changed, 62 insertions, 43 deletions
@@ -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. |