admin: /config and /id endpoints

This integrates a feature that was previously reserved for enterprise
users, according to https://github.com/caddyserver/caddy/issues/2786.

The /config and /id endpoints make granular config changes possible as
well as the exporting of the current configuration.

The /load endpoint has been modified to wrap the /config handler so that
the currently-running config can always be available for export. The
difference is that /load allows configs of varying formats and converts
them using config adapters. The adapted config is then processed with
/config as JSON. The /config and /id endpoints accept only JSON.
This commit is contained in:
Matthew Holt
2019-10-09 19:10:00 -06:00
parent 53dd600b4d
commit 03306e646e
4 changed files with 493 additions and 16 deletions

View File

@ -87,7 +87,7 @@ func StartAdmin(initialConfigJSON []byte) error {
return fmt.Errorf("parsing admin listener address: %v", err)
}
if len(listenAddrs) != 1 {
return fmt.Errorf("admin endpoint must have exactly one listener; cannot listen on %v", listenAddrs)
return fmt.Errorf("admin endpoint must have exactly one address; cannot listen on %v", listenAddrs)
}
ln, err := net.Listen(netw, listenAddrs[0])
if err != nil {
@ -120,7 +120,7 @@ func StartAdmin(initialConfigJSON []byte) error {
ReadTimeout: 5 * time.Second,
ReadHeaderTimeout: 5 * time.Second,
IdleTimeout: 5 * time.Second,
MaxHeaderBytes: 1024 * 256,
MaxHeaderBytes: 1024 * 64,
}
go cfgEndptSrv.Serve(ln)
@ -169,14 +169,11 @@ type AdminRoute struct {
}
func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
r.Close = true
if r.Method != http.MethodPost {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return
}
var payload io.Reader = r.Body
// if the config is formatted other than Caddy's native
// JSON, we need to adapt it before loading it
if ctHeader := r.Header.Get("Content-Type"); ctHeader != "" {
@ -215,16 +212,15 @@ func handleLoadConfig(w http.ResponseWriter, r *http.Request) {
}
w.Write(respBody)
}
payload = bytes.NewReader(result)
// replace original request body with adapted JSON
r.Body.Close()
r.Body = ioutil.NopCloser(bytes.NewReader(result))
}
}
err := Load(payload)
if err != nil {
log.Printf("[ADMIN][ERROR] loading config: %v", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// pass this off to the /config/ endpoint
r.URL.Path = "/" + rawConfigKey + "/"
handleConfig(w, r)
}
func handleStop(w http.ResponseWriter, r *http.Request) {