|
@ -18,6 +18,7 @@ import ( |
|
|
"bytes" |
|
|
"bytes" |
|
|
"context" |
|
|
"context" |
|
|
"encoding/json" |
|
|
"encoding/json" |
|
|
|
|
|
"errors" |
|
|
"fmt" |
|
|
"fmt" |
|
|
"io" |
|
|
"io" |
|
|
"log" |
|
|
"log" |
|
@ -110,14 +111,19 @@ func Load(cfgJSON []byte, forceReload bool) error { |
|
|
} |
|
|
} |
|
|
}() |
|
|
}() |
|
|
|
|
|
|
|
|
return changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload) |
|
|
err := changeConfig(http.MethodPost, "/"+rawConfigKey, cfgJSON, forceReload) |
|
|
|
|
|
if errors.Is(err, errSameConfig) { |
|
|
|
|
|
err = nil // not really an error
|
|
|
|
|
|
} |
|
|
|
|
|
return err |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// changeConfig changes the current config (rawCfg) according to the
|
|
|
// changeConfig changes the current config (rawCfg) according to the
|
|
|
// method, traversed via the given path, and uses the given input as
|
|
|
// method, traversed via the given path, and uses the given input as
|
|
|
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
|
|
|
// the new value (if applicable; i.e. "DELETE" doesn't have an input).
|
|
|
// If the resulting config is the same as the previous, no reload will
|
|
|
// If the resulting config is the same as the previous, no reload will
|
|
|
// occur unless forceReload is true. This function is safe for
|
|
|
// occur unless forceReload is true. If the config is unchanged and not
|
|
|
|
|
|
// forcefully reloaded, then errConfigUnchanged This function is safe for
|
|
|
// concurrent use.
|
|
|
// concurrent use.
|
|
|
func changeConfig(method, path string, input []byte, forceReload bool) error { |
|
|
func changeConfig(method, path string, input []byte, forceReload bool) error { |
|
|
switch method { |
|
|
switch method { |
|
@ -149,7 +155,7 @@ func changeConfig(method, path string, input []byte, forceReload bool) error { |
|
|
// if nothing changed, no need to do a whole reload unless the client forces it
|
|
|
// if nothing changed, no need to do a whole reload unless the client forces it
|
|
|
if !forceReload && bytes.Equal(rawCfgJSON, newCfg) { |
|
|
if !forceReload && bytes.Equal(rawCfgJSON, newCfg) { |
|
|
Log().Info("config is unchanged") |
|
|
Log().Info("config is unchanged") |
|
|
return nil |
|
|
return errSameConfig |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// find any IDs in this config and index them
|
|
|
// find any IDs in this config and index them
|
|
@ -492,39 +498,47 @@ func finishSettingUp(ctx Context, cfg *Config) error { |
|
|
zap.String("module", val.(Module).CaddyModule().ID.Name()), |
|
|
zap.String("module", val.(Module).CaddyModule().ID.Name()), |
|
|
zap.Int("load_delay", int(cfg.Admin.Config.LoadDelay))) |
|
|
zap.Int("load_delay", int(cfg.Admin.Config.LoadDelay))) |
|
|
|
|
|
|
|
|
runLoadedConfig := func(config []byte) { |
|
|
runLoadedConfig := func(config []byte) error { |
|
|
logger.Info("applying dynamically-loaded config") |
|
|
logger.Info("applying dynamically-loaded config") |
|
|
err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, false) |
|
|
err := changeConfig(http.MethodPost, "/"+rawConfigKey, config, false) |
|
|
if err == nil { |
|
|
if errors.Is(err, errSameConfig) { |
|
|
logger.Info("successfully applied dynamically-loaded config") |
|
|
return err |
|
|
} else { |
|
|
} |
|
|
|
|
|
if err != nil { |
|
|
logger.Error("failed to run dynamically-loaded config", zap.Error(err)) |
|
|
logger.Error("failed to run dynamically-loaded config", zap.Error(err)) |
|
|
|
|
|
return err |
|
|
} |
|
|
} |
|
|
|
|
|
logger.Info("successfully applied dynamically-loaded config") |
|
|
|
|
|
return nil |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if cfg.Admin.Config.LoadDelay > 0 { |
|
|
if cfg.Admin.Config.LoadDelay > 0 { |
|
|
go func() { |
|
|
go func() { |
|
|
// the loop is here only to iterate if there is an error or a no-op config load,
|
|
|
// the loop is here to iterate ONLY if there is an error, a no-op config load,
|
|
|
// in which case we simply wait the delay and try again
|
|
|
// or an unchanged config; in which case we simply wait the delay and try again
|
|
|
for { |
|
|
for { |
|
|
timer := time.NewTimer(time.Duration(cfg.Admin.Config.LoadDelay)) |
|
|
timer := time.NewTimer(time.Duration(cfg.Admin.Config.LoadDelay)) |
|
|
select { |
|
|
select { |
|
|
case <-timer.C: |
|
|
case <-timer.C: |
|
|
loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx) |
|
|
loadedConfig, err := val.(ConfigLoader).LoadConfig(ctx) |
|
|
if err != nil { |
|
|
if err != nil { |
|
|
Log().Error("failed loading dynamic config; will retry", zap.Error(err)) |
|
|
logger.Error("failed loading dynamic config; will retry", zap.Error(err)) |
|
|
continue |
|
|
continue |
|
|
} |
|
|
} |
|
|
if loadedConfig == nil { |
|
|
if loadedConfig == nil { |
|
|
Log().Info("dynamically-loaded config was nil; will retry") |
|
|
logger.Info("dynamically-loaded config was nil; will retry") |
|
|
|
|
|
continue |
|
|
|
|
|
} |
|
|
|
|
|
err = runLoadedConfig(loadedConfig) |
|
|
|
|
|
if errors.Is(err, errSameConfig) { |
|
|
|
|
|
logger.Info("dynamically-loaded config was unchanged; will retry") |
|
|
continue |
|
|
continue |
|
|
} |
|
|
} |
|
|
runLoadedConfig(loadedConfig) |
|
|
|
|
|
case <-ctx.Done(): |
|
|
case <-ctx.Done(): |
|
|
if !timer.Stop() { |
|
|
if !timer.Stop() { |
|
|
<-timer.C |
|
|
<-timer.C |
|
|
} |
|
|
} |
|
|
Log().Info("stopping dynamic config loading") |
|
|
logger.Info("stopping dynamic config loading") |
|
|
} |
|
|
} |
|
|
break |
|
|
break |
|
|
} |
|
|
} |
|
@ -798,5 +812,10 @@ var ( |
|
|
rawCfgIndex map[string]string |
|
|
rawCfgIndex map[string]string |
|
|
) |
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
// errSameConfig is returned if the new config is the same
|
|
|
|
|
|
// as the old one. This isn't usually an actual, actionable
|
|
|
|
|
|
// error; it's mostly a sentinel value.
|
|
|
|
|
|
var errSameConfig = errors.New("config is unchanged") |
|
|
|
|
|
|
|
|
// ImportPath is the package import path for Caddy core.
|
|
|
// ImportPath is the package import path for Caddy core.
|
|
|
const ImportPath = "github.com/caddyserver/caddy/v2" |
|
|
const ImportPath = "github.com/caddyserver/caddy/v2" |
|
|