admin listener as opt-in for initial config (#2834)

* Always cleanup admin endpoint first

* Error out if no config has been set (#2833)

* Ignore explicitly missing admin config (#2833)

* Separate config loading from admin initialization (#2833)

* Add admin option to specify admin listener address (#2833)

* Use zap for reporting admin endpoint status
This commit is contained in:
Andreas Schneider 2019-10-30 22:12:42 +01:00 committed by Matt Holt
parent 4611537f06
commit 432b94239d
4 changed files with 55 additions and 20 deletions

View file

@ -18,6 +18,7 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -42,6 +43,8 @@ var (
cfgEndptSrvMu sync.Mutex cfgEndptSrvMu sync.Mutex
) )
var ErrAdminInterfaceNotConfigured = errors.New("no admin configuration has been set")
// AdminConfig configures the admin endpoint. // AdminConfig configures the admin endpoint.
type AdminConfig struct { type AdminConfig struct {
Listen string `json:"listen,omitempty"` Listen string `json:"listen,omitempty"`
@ -60,10 +63,24 @@ var DefaultAdminConfig = &AdminConfig{
// in the format of JSON bytes. It opens a listener // in the format of JSON bytes. It opens a listener
// resource. When no longer needed, StopAdmin should // resource. When no longer needed, StopAdmin should
// be called. // be called.
// If no configuration is given, a default listener is
// started. If a configuration is given that does NOT
// specifically configure the admin interface,
// `ErrAdminInterfaceNotConfigured` is returned and no
// listener is initialized.
func StartAdmin(initialConfigJSON []byte) error { func StartAdmin(initialConfigJSON []byte) error {
cfgEndptSrvMu.Lock() cfgEndptSrvMu.Lock()
defer cfgEndptSrvMu.Unlock() defer cfgEndptSrvMu.Unlock()
if cfgEndptSrv != nil {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := cfgEndptSrv.Shutdown(ctx)
if err != nil {
return fmt.Errorf("shutting down old admin endpoint: %v", err)
}
}
adminConfig := DefaultAdminConfig adminConfig := DefaultAdminConfig
if len(initialConfigJSON) > 0 { if len(initialConfigJSON) > 0 {
var config *Config var config *Config
@ -71,17 +88,12 @@ func StartAdmin(initialConfigJSON []byte) error {
if err != nil { if err != nil {
return fmt.Errorf("unmarshaling bootstrap config: %v", err) return fmt.Errorf("unmarshaling bootstrap config: %v", err)
} }
if config != nil && config.Admin != nil { if config != nil {
if config.Admin == nil {
return ErrAdminInterfaceNotConfigured
}
adminConfig = config.Admin adminConfig = config.Admin
} }
if cfgEndptSrv != nil {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
err := cfgEndptSrv.Shutdown(ctx)
if err != nil {
return fmt.Errorf("shutting down old admin endpoint: %v", err)
}
}
} }
// extract a singular listener address // extract a singular listener address
@ -136,15 +148,7 @@ func StartAdmin(initialConfigJSON []byte) error {
go cfgEndptSrv.Serve(ln) go cfgEndptSrv.Serve(ln)
fmt.Println("Caddy 2 admin endpoint listening on", adminConfig.Listen) Log().Named("admin").Info("Caddy 2 admin endpoint started.", zap.String("listenAddress", adminConfig.Listen))
if len(initialConfigJSON) > 0 {
err := Load(bytes.NewReader(initialConfigJSON))
if err != nil {
return fmt.Errorf("loading initial config: %v", err)
}
fmt.Println("Caddy 2 serving initial configuration")
}
return nil return nil
} }

View file

@ -74,6 +74,8 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
val, err = parseOptACMECA(disp) val, err = parseOptACMECA(disp)
case "email": case "email":
val, err = parseOptEmail(disp) val, err = parseOptEmail(disp)
case "admin":
val, err = parseOptAdmin(disp)
default: default:
return nil, warnings, fmt.Errorf("unrecognized parameter name: %s", dir) return nil, warnings, fmt.Errorf("unrecognized parameter name: %s", dir)
} }
@ -254,6 +256,9 @@ func (st ServerType) Setup(originalServerBlocks []caddyfile.ServerBlock,
storageCvtr.(caddy.Module).CaddyModule().ID(), storageCvtr.(caddy.Module).CaddyModule().ID(),
&warnings) &warnings)
} }
if adminConfig, ok := options["admin"].(string); ok && adminConfig != "" {
cfg.Admin = &caddy.AdminConfig{Listen: adminConfig}
}
return cfg, warnings, nil return cfg, warnings, nil
} }

View file

@ -132,3 +132,17 @@ func parseOptEmail(d *caddyfile.Dispenser) (string, error) {
} }
return val, nil return val, nil
} }
func parseOptAdmin(d *caddyfile.Dispenser) (string, error) {
if d.Next() {
var listenAddress string
d.AllArgs(&listenAddress)
if listenAddress == "" {
listenAddress = caddy.DefaultAdminListen
}
return listenAddress, nil
}
return "", nil
}

View file

@ -161,12 +161,24 @@ func cmdRun(fl Flags) (int, error) {
certmagic.UserAgent = "Caddy/" + cleanModVersion certmagic.UserAgent = "Caddy/" + cleanModVersion
// start the admin endpoint along with any initial config // start the admin endpoint along with any initial config
// a configuration without admin config is considered fine
// but does not enable the admin endpoint at all
err = caddy.StartAdmin(config) err = caddy.StartAdmin(config)
if err != nil { if err == nil {
defer caddy.StopAdmin()
} else if err != caddy.ErrAdminInterfaceNotConfigured {
return caddy.ExitCodeFailedStartup, return caddy.ExitCodeFailedStartup,
fmt.Errorf("starting caddy administration endpoint: %v", err) fmt.Errorf("starting caddy administration endpoint: %v", err)
} }
defer caddy.StopAdmin()
// if a config has been supplied, load it as initial config
if len(config) > 0 {
err := caddy.Load(bytes.NewReader(config))
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
}
caddy.Log().Named("admin").Info("Caddy 2 serving initial configuration")
}
// if we are to report to another process the successful start // if we are to report to another process the successful start
// of the server, do so now by echoing back contents of stdin // of the server, do so now by echoing back contents of stdin