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
// be allowed.
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,
@ -775,7 +785,11 @@ traverseLoop:
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 {
return idRegexp.ReplaceAllFunc(rawJSON, func(in []byte) []byte {
// matches with a comma on both sides (when "@id" property is

View File

@ -20,6 +20,7 @@ import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"path"
@ -30,6 +31,7 @@ import (
"time"
"github.com/mholt/certmagic"
"go.uber.org/zap"
)
// 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
// our old representation of caddy's actual config
err = unsyncedDecodeAndRun(newCfg)
@ -232,15 +227,19 @@ func indexConfigObjects(ptr interface{}, configPath string, index map[string]str
return nil
}
// unsyncedDecodeAndRun decodes cfgJSON and runs
// it as the new config, replacing any other
// current config. It does not update the raw
// config state, as this is a lower-level function;
// most callers will want to use Load instead.
// A write lock on currentCfgMu is required!
// unsyncedDecodeAndRun removes any meta fields (like @id tags)
// from cfgJSON, decodes the result into a *Config, and runs
// it as the new config, replacing any other current config.
// It does NOT update the raw config state, as this is a
// lower-level function; most callers will want to use Load
// instead. A write lock on currentCfgMu is required!
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
err := strictUnmarshalJSON(cfgJSON, &newCfg)
err := strictUnmarshalJSON(strippedCfgJSON, &newCfg)
if err != nil {
return err
}
@ -258,6 +257,22 @@ func unsyncedDecodeAndRun(cfgJSON []byte) error {
// Stop, Cleanup each old app
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
}

View File

@ -27,6 +27,7 @@ import (
"os"
"os/exec"
"reflect"
"runtime"
"runtime/debug"
"sort"
"strings"
@ -141,6 +142,7 @@ func cmdStart(fl Flags) (int, error) {
func cmdRun(fl Flags) (int, error) {
runCmdConfigFlag := fl.String("config")
runCmdConfigAdapterFlag := fl.String("adapter")
runCmdResumeFlag := fl.Bool("resume")
runCmdPrintEnvFlag := fl.Bool("environ")
runCmdPingbackFlag := fl.String("pingback")
@ -149,14 +151,32 @@ func cmdRun(fl Flags) (int, error) {
printEnvironment()
}
// get the config in caddy's native format
config, err := loadConfig(runCmdConfigFlag, runCmdConfigAdapterFlag)
if err != nil {
return caddy.ExitCodeFailedStartup, err
}
// TODO: This is TEMPORARY, until the RCs
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 {
return caddy.ExitCodeFailedStartup, err
}
}
// set a fitting User-Agent for ACME requests
goModule := caddy.GoModule()
cleanModVersion := strings.TrimPrefix(goModule.Version, "v")
@ -167,9 +187,7 @@ func cmdRun(fl Flags) (int, error) {
if err != nil {
return caddy.ExitCodeFailedStartup, fmt.Errorf("loading initial config: %v", err)
}
if len(config) > 0 {
caddy.Log().Named("admin").Info("Caddy 2 serving initial configuration")
}
caddy.Log().Info("serving initial configuration")
// if we are to report to another process the successful start
// 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
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 {
fs := flag.NewFlagSet("run", flag.ExitOnError)
fs.String("config", "", "Configuration file")
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.String("pingback", "", "Echo confirmation bytes to this address on success")
return fs

View File

@ -110,6 +110,9 @@ func loadConfig(configFile, adapterName string) ([]byte, error) {
if err != nil {
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 == "" {
// as a special case when no config file or adapter
// is specified, see if the Caddyfile adapter is
@ -126,6 +129,7 @@ func loadConfig(configFile, adapterName string) ([]byte, error) {
} else {
// success reading default Caddyfile
configFile = "Caddyfile"
caddy.Log().Info("using adjacent Caddyfile")
}
}
}
@ -225,6 +229,21 @@ func flagHelp(fs *flag.FlagSet) string {
}
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() {
fmt.Println(v)
}