Config auto-save; run --resume flag; update environ output (close #2903)

Config auto-saving is on by default and can be disabled. The --environ
flag (or environ subcommand) now print more useful information from
Caddy and the runtime, including some nifty paths.
This commit is contained in:
Matthew Holt 2019-12-31 16:56:19 -07:00
parent 984d384d14
commit 5a0603ed72
No known key found for this signature in database
GPG key ID: 2A349DD577D586A5
5 changed files with 94 additions and 24 deletions

View file

@ -66,6 +66,16 @@ type AdminConfig struct {
// will be the default value. If set but empty, no origins will // will be the default value. If set but empty, no origins will
// be allowed. // be allowed.
Origins []string `json:"origins,omitempty"` Origins []string `json:"origins,omitempty"`
// Options related to configuration management.
Config *ConfigSettings `json:"config,omitempty"`
}
// ConfigSettings configures the, uh, configuration... and
// management thereof.
type ConfigSettings struct {
// Whether to keep a copy of the active config on disk. Default is true.
Persist *bool `json:"persist,omitempty"`
} }
// listenAddr extracts a singular listen address from ac.Listen, // listenAddr extracts a singular listen address from ac.Listen,
@ -775,7 +785,11 @@ traverseLoop:
return nil return nil
} }
// RemoveMetaFields removes meta fields like "@id" from a JSON message. // RemoveMetaFields removes meta fields like "@id" from a JSON message
// by using a simple regular expression. (An alternate way to do this
// would be to delete them from the raw, map[string]interface{}
// representation as they are indexed, then iterate the index we made
// and add them back after encoding as JSON, but this is simpler.)
func RemoveMetaFields(rawJSON []byte) []byte { func RemoveMetaFields(rawJSON []byte) []byte {
return idRegexp.ReplaceAllFunc(rawJSON, func(in []byte) []byte { return idRegexp.ReplaceAllFunc(rawJSON, func(in []byte) []byte {
// matches with a comma on both sides (when "@id" property is // matches with a comma on both sides (when "@id" property is

View file

@ -20,6 +20,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"path" "path"
@ -30,6 +31,7 @@ import (
"time" "time"
"github.com/mholt/certmagic" "github.com/mholt/certmagic"
"go.uber.org/zap"
) )
// Config is the top (or beginning) of the Caddy configuration structure. // Config is the top (or beginning) of the Caddy configuration structure.
@ -148,13 +150,6 @@ func changeConfig(method, path string, input []byte, forceReload bool) error {
} }
} }
// remove any @id fields from the JSON, which would cause
// loading to break since the field wouldn't be recognized
// (an alternate way to do this would be to delete them from
// rawCfg as they are indexed, then iterate the index we made
// and add them back after encoding as JSON)
newCfg = RemoveMetaFields(newCfg)
// load this new config; if it fails, we need to revert to // load this new config; if it fails, we need to revert to
// our old representation of caddy's actual config // our old representation of caddy's actual config
err = unsyncedDecodeAndRun(newCfg) err = unsyncedDecodeAndRun(newCfg)
@ -232,15 +227,19 @@ func indexConfigObjects(ptr interface{}, configPath string, index map[string]str
return nil return nil
} }
// unsyncedDecodeAndRun decodes cfgJSON and runs // unsyncedDecodeAndRun removes any meta fields (like @id tags)
// it as the new config, replacing any other // from cfgJSON, decodes the result into a *Config, and runs
// current config. It does not update the raw // it as the new config, replacing any other current config.
// config state, as this is a lower-level function; // It does NOT update the raw config state, as this is a
// most callers will want to use Load instead. // lower-level function; most callers will want to use Load
// A write lock on currentCfgMu is required! // instead. A write lock on currentCfgMu is required!
func unsyncedDecodeAndRun(cfgJSON []byte) error { func unsyncedDecodeAndRun(cfgJSON []byte) error {
// remove any @id fields from the JSON, which would cause
// loading to break since the field wouldn't be recognized
strippedCfgJSON := RemoveMetaFields(cfgJSON)
var newCfg *Config var newCfg *Config
err := strictUnmarshalJSON(cfgJSON, &newCfg) err := strictUnmarshalJSON(strippedCfgJSON, &newCfg)
if err != nil { if err != nil {
return err return err
} }
@ -258,6 +257,22 @@ func unsyncedDecodeAndRun(cfgJSON []byte) error {
// Stop, Cleanup each old app // Stop, Cleanup each old app
unsyncedStop(oldCfg) unsyncedStop(oldCfg)
// autosave a non-nil config, if not disabled
if newCfg != nil &&
(newCfg.Admin == nil ||
newCfg.Admin.Config == nil ||
newCfg.Admin.Config.Persist == nil ||
*newCfg.Admin.Config.Persist) {
err := ioutil.WriteFile(ConfigAutosavePath, cfgJSON, 0600)
if err == nil {
Log().Info("autosaved config", zap.String("file", ConfigAutosavePath))
} else {
Log().Error("unable to autosave config",
zap.String("file", ConfigAutosavePath),
zap.Error(err))
}
}
return nil return nil
} }

View file

@ -27,6 +27,7 @@ import (
"os" "os"
"os/exec" "os/exec"
"reflect" "reflect"
"runtime"
"runtime/debug" "runtime/debug"
"sort" "sort"
"strings" "strings"
@ -141,6 +142,7 @@ func cmdStart(fl Flags) (int, error) {
func cmdRun(fl Flags) (int, error) { func cmdRun(fl Flags) (int, error) {
runCmdConfigFlag := fl.String("config") runCmdConfigFlag := fl.String("config")
runCmdConfigAdapterFlag := fl.String("adapter") runCmdConfigAdapterFlag := fl.String("adapter")
runCmdResumeFlag := fl.Bool("resume")
runCmdPrintEnvFlag := fl.Bool("environ") runCmdPrintEnvFlag := fl.Bool("environ")
runCmdPingbackFlag := fl.String("pingback") runCmdPingbackFlag := fl.String("pingback")
@ -149,13 +151,31 @@ func cmdRun(fl Flags) (int, error) {
printEnvironment() printEnvironment()
} }
// get the config in caddy's native format // TODO: This is TEMPORARY, until the RCs
config, err := loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag) moveStorage()
// load the config, depending on flags
var config []byte
var err error
if runCmdResumeFlag {
config, err = ioutil.ReadFile(caddy.ConfigAutosavePath)
if os.IsNotExist(err) {
// not a bad error; just can't resume if autosave file doesn't exist
caddy.Log().Info("no autosave file exists", zap.String("autosave_file", caddy.ConfigAutosavePath))
runCmdResumeFlag = false
} else if err != nil {
return caddy.ExitCodeFailedStartup, err
} else {
caddy.Log().Info("resuming from last configuration", zap.String("autosave_file", caddy.ConfigAutosavePath))
}
}
// we don't use 'else' here since this value might have been changed in 'if' block; i.e. not mutually exclusive
if !runCmdResumeFlag {
config, err = loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, err return caddy.ExitCodeFailedStartup, err
} }
// TODO: This is TEMPORARY, until the RCs }
moveStorage()
// set a fitting User-Agent for ACME requests // set a fitting User-Agent for ACME requests
goModule := caddy.GoModule() goModule := caddy.GoModule()
@ -167,9 +187,7 @@ func cmdRun(fl Flags) (int, error) {
if err != nil { if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err) return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
} }
if len(config) > 0 { caddy.Log().Info("serving initial configuration")
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

View file

@ -116,11 +116,15 @@ line flags.
If --environ is specified, the environment as seen by the Caddy process will If --environ is specified, the environment as seen by the Caddy process will
be printed before starting. This is the same as the environ command but does be printed before starting. This is the same as the environ command but does
not quit after printing, and can be useful for troubleshooting.`, not quit after printing, and can be useful for troubleshooting.
The --resume flag will override the --config flag if there is a config auto-
save file. It is not an error if --resume is used and no autosave file exists.`,
Flags: func() *flag.FlagSet { Flags: func() *flag.FlagSet {
fs := flag.NewFlagSet("run", flag.ExitOnError) fs := flag.NewFlagSet("run", flag.ExitOnError)
fs.String("config", "", "Configuration file") fs.String("config", "", "Configuration file")
fs.String("adapter", "", "Name of config adapter to apply") fs.String("adapter", "", "Name of config adapter to apply")
fs.Bool("resume", false, "Use saved config, if any (and prefer over --config file)")
fs.Bool("environ", false, "Print environment") fs.Bool("environ", false, "Print environment")
fs.String("pingback", "", "Echo confirmation bytes to this address on success") fs.String("pingback", "", "Echo confirmation bytes to this address on success")
return fs return fs

View file

@ -110,6 +110,9 @@ func loadConfig(configFile, adapterName string) ([]byte, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("reading config file: %v", err) return nil, fmt.Errorf("reading config file: %v", err)
} }
caddy.Log().Info("using provided configuration",
zap.String("config_file", configFile),
zap.String("config_adapter", adapterName))
} else if adapterName == "" { } else if adapterName == "" {
// as a special case when no config file or adapter // as a special case when no config file or adapter
// is specified, see if the Caddyfile adapter is // is specified, see if the Caddyfile adapter is
@ -126,6 +129,7 @@ func loadConfig(configFile, adapterName string) ([]byte, error) {
} else { } else {
// success reading default Caddyfile // success reading default Caddyfile
configFile = "Caddyfile" configFile = "Caddyfile"
caddy.Log().Info("using adjacent Caddyfile")
} }
} }
} }
@ -225,6 +229,21 @@ func flagHelp(fs *flag.FlagSet) string {
} }
func printEnvironment() { func printEnvironment() {
fmt.Printf("caddy.HomeDir=%s\n", caddy.HomeDir())
fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir())
fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir())
fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath)
fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS)
fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH)
fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler)
fmt.Printf("runtime.NumCPU=%d\n", runtime.NumCPU())
fmt.Printf("runtime.GOMAXPROCS=%d\n", runtime.GOMAXPROCS(0))
fmt.Printf("runtime.Version=%s\n", runtime.Version())
cwd, err := os.Getwd()
if err != nil {
cwd = fmt.Sprintf("<error: %v>", err)
}
fmt.Printf("os.Getwd=%s\n\n", cwd)
for _, v := range os.Environ() { for _, v := range os.Environ() {
fmt.Println(v) fmt.Println(v)
} }