Skip to content

Commit a10117f

Browse files
authored
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.
1 parent 101d3e7 commit a10117f

File tree

1 file changed

+62
-43
lines changed

1 file changed

+62
-43
lines changed

caddy.go

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,58 @@ func unsyncedDecodeAndRun(cfgJSON []byte, allowPersist bool) error {
397397
// will want to use Run instead, which also
398398
// updates the config's raw state.
399399
func run(newCfg *Config, start bool) (Context, error) {
400+
ctx, err := provisionContext(newCfg, start)
401+
if err != nil {
402+
return ctx, err
403+
}
404+
405+
if !start {
406+
return ctx, nil
407+
}
408+
409+
// Provision any admin routers which may need to access
410+
// some of the other apps at runtime
411+
err = ctx.cfg.Admin.provisionAdminRouters(ctx)
412+
if err != nil {
413+
return ctx, err
414+
}
415+
416+
// Start
417+
err = func() error {
418+
started := make([]string, 0, len(ctx.cfg.apps))
419+
for name, a := range ctx.cfg.apps {
420+
err := a.Start()
421+
if err != nil {
422+
// an app failed to start, so we need to stop
423+
// all other apps that were already started
424+
for _, otherAppName := range started {
425+
err2 := ctx.cfg.apps[otherAppName].Stop()
426+
if err2 != nil {
427+
err = fmt.Errorf("%v; additionally, aborting app %s: %v",
428+
err, otherAppName, err2)
429+
}
430+
}
431+
return fmt.Errorf("%s app module: start: %v", name, err)
432+
}
433+
started = append(started, name)
434+
}
435+
return nil
436+
}()
437+
if err != nil {
438+
return ctx, err
439+
}
440+
441+
// now that the user's config is running, finish setting up anything else,
442+
// such as remote admin endpoint, config loader, etc.
443+
return ctx, finishSettingUp(ctx, ctx.cfg)
444+
}
445+
446+
// provisionContext creates a new context from the given configuration and provisions
447+
// storage and apps.
448+
// If `newCfg` is nil a new empty configuration will be created.
449+
// If `replaceAdminServer` is true any currently active admin server will be replaced
450+
// with a new admin server based on the provided configuration.
451+
func provisionContext(newCfg *Config, replaceAdminServer bool) (Context, error) {
400452
// because we will need to roll back any state
401453
// modifications if this function errors, we
402454
// keep a single error value and scope all
@@ -444,7 +496,7 @@ func run(newCfg *Config, start bool) (Context, error) {
444496
}
445497

446498
// start the admin endpoint (and stop any prior one)
447-
if start {
499+
if replaceAdminServer {
448500
err = replaceLocalAdminServer(newCfg)
449501
if err != nil {
450502
return ctx, fmt.Errorf("starting caddy administration endpoint: %v", err)
@@ -491,49 +543,16 @@ func run(newCfg *Config, start bool) (Context, error) {
491543
}
492544
return nil
493545
}()
494-
if err != nil {
495-
return ctx, err
496-
}
497-
498-
if !start {
499-
return ctx, nil
500-
}
501-
502-
// Provision any admin routers which may need to access
503-
// some of the other apps at runtime
504-
err = newCfg.Admin.provisionAdminRouters(ctx)
505-
if err != nil {
506-
return ctx, err
507-
}
508-
509-
// Start
510-
err = func() error {
511-
started := make([]string, 0, len(newCfg.apps))
512-
for name, a := range newCfg.apps {
513-
err := a.Start()
514-
if err != nil {
515-
// an app failed to start, so we need to stop
516-
// all other apps that were already started
517-
for _, otherAppName := range started {
518-
err2 := newCfg.apps[otherAppName].Stop()
519-
if err2 != nil {
520-
err = fmt.Errorf("%v; additionally, aborting app %s: %v",
521-
err, otherAppName, err2)
522-
}
523-
}
524-
return fmt.Errorf("%s app module: start: %v", name, err)
525-
}
526-
started = append(started, name)
527-
}
528-
return nil
529-
}()
530-
if err != nil {
531-
return ctx, err
532-
}
546+
return ctx, err
547+
}
533548

534-
// now that the user's config is running, finish setting up anything else,
535-
// such as remote admin endpoint, config loader, etc.
536-
return ctx, finishSettingUp(ctx, newCfg)
549+
// ProvisionContext creates a new context from the configuration and provisions storage
550+
// and app modules.
551+
// The function is intended for testing and advanced use cases only, typically `Run` should be
552+
// use to ensure a fully functional caddy instance.
553+
// EXPERIMENTAL: While this is public the interface and implementation details of this function may change.
554+
func ProvisionContext(newCfg *Config) (Context, error) {
555+
return provisionContext(newCfg, false)
537556
}
538557

539558
// finishSettingUp should be run after all apps have successfully started.

0 commit comments

Comments
 (0)