adds ability to update and save loaded configuration

- internal configuration values are stripped before save
- adds method to create a backup of the current configuration
This commit is contained in:
Andrew Martinez
2022-01-21 15:18:27 -05:00
parent 575a0272c6
commit 86e66cf4d3
+135 -2
View File
@@ -30,14 +30,34 @@ import (
"github.com/pkg/errors"
"github.com/spf13/pflag"
"gopkg.in/yaml.v2"
"io"
"io/ioutil"
"os"
"time"
)
const (
// FlagsCfgMapKey is used as a key in the source configuration map to pass flags from
// higher levels (i.e. CLI arguments) down through the stack w/o colliding w/ file
// based configuration values
FlagsCfgMapKey = "@flags"
// PathMapKey is used to store a loaded configuration file's source path
PathMapKey = "@file"
// CtrlMapKey is the string key for the ctrl section
CtrlMapKey = "ctrl"
// CtrlEndpointMapKey is the string key for the ctrl.endpoint section
CtrlEndpointMapKey = "endpoint"
)
// internalConfigKeys is used to distinguish internally defined configuration vs file configuration
var internalConfigKeys = []string{
PathMapKey,
FlagsCfgMapKey,
}
func LoadConfigMap(path string) (map[interface{}]interface{}, error) {
yamlBytes, err := ioutil.ReadFile(path)
if err != nil {
@@ -51,6 +71,8 @@ func LoadConfigMap(path string) (map[interface{}]interface{}, error) {
config.InjectEnv(cfgmap)
cfgmap[PathMapKey] = path
return cfgmap, nil
}
@@ -98,6 +120,7 @@ type Config struct {
}
Plugins []string
src map[interface{}]interface{}
path string
}
func (config *Config) Configure(sub config.Subconfig) error {
@@ -108,6 +131,112 @@ func (config *Config) SetFlags(flags map[string]*pflag.Flag) {
SetConfigMapFlags(config.src, flags)
}
// CreateBackup will attempt to use the current path value to create a backup of
// the file on disk. The resulting file path is returned.
func (config *Config) CreateBackup() (string, error) {
source, err := os.Open(config.path)
if err != nil {
return "", fmt.Errorf("could not open path %s: %v", config.path, err)
}
defer func() { _ = source.Close() }()
destPath := config.path + ".backup." + time.Now().Format("20060102150405")
destination, err := os.Create(destPath)
if err != nil {
return "", fmt.Errorf("could not create backup file: %v", err)
}
defer func() { _ = destination.Close() }()
if _, err := io.Copy(destination, source); err != nil {
return "", fmt.Errorf("could not copy to backup file: %v", err)
}
return destPath, nil
}
func deepCopyMap(src map[interface{}]interface{}, dest map[interface{}]interface{}) {
for key, value := range src {
switch src[key].(type) {
case map[interface{}]interface{}:
dest[key] = map[interface{}]interface{}{}
deepCopyMap(src[key].(map[interface{}]interface{}), dest[key].(map[interface{}]interface{}))
default:
dest[key] = value
}
}
}
// cleanSrcCopy returns a copy of the current src map[interface{}]interface{} without internal
// keys like @FlagsCfgMapKey and @PathMapKey.
func (config *Config) cleanSrcCopy() map[interface{}]interface{} {
out := map[interface{}]interface{}{}
deepCopyMap(config.src, out)
for _, internalKey := range internalConfigKeys {
delete(out, internalKey)
}
return out
}
// Save attempts to take the current config's src attribute and Save it as
// yaml to the path value.
func (config *Config) Save() error {
if config.path == "" {
return errors.New("no path provided in configuration, cannot save")
}
if _, err := os.Stat(config.path); err != nil {
return fmt.Errorf("invalid path %s: %v", config.path, err)
}
outSrc := config.cleanSrcCopy()
out, err := yaml.Marshal(outSrc)
if err != nil {
return err
}
file, err := os.Create(config.path)
if err != nil {
return err
}
defer func() { _ = file.Close() }()
_, err = file.Write(out)
return err
}
// UpdateControllerEndpoint updates the runtime configuration address of the controller and the
// internal map configuration.
func (config *Config) UpdateControllerEndpoint(address string) error {
if parsedAddress, err := transport.ParseAddress(address); parsedAddress != nil && err == nil {
if config.Ctrl.Endpoint.String() != address {
//config file update
if ctrlVal, ok := config.src[CtrlMapKey]; ok {
if ctrlMap, ok := ctrlVal.(map[interface{}]interface{}); ok {
ctrlMap[CtrlEndpointMapKey] = address
} else {
return errors.New("source ctrl found but not map[interface{}]interface{}")
}
} else {
return errors.New("source ctrl key not found")
}
//runtime update
config.Ctrl.Endpoint = parsedAddress
}
} else {
return fmt.Errorf("could not parse address: %v", err)
}
return nil
}
func LoadConfig(path string) (*Config, error) {
cfgmap, err := LoadConfigMap(path)
@@ -137,6 +266,10 @@ func LoadConfig(path string) (*Config, error) {
cfg.Id = identity.NewIdentity(id)
}
if value, found := cfgmap[PathMapKey]; found {
cfg.path = value.(string)
}
cfg.Forwarder = forwarder.DefaultOptions()
if value, found := cfgmap["forwarder"]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
@@ -190,9 +323,9 @@ func LoadConfig(path string) (*Config, error) {
cfg.Ctrl.DefaultRequestTimeout = 5 * time.Second
cfg.Ctrl.Options = channel2.DefaultOptions()
if value, found := cfgmap["ctrl"]; found {
if value, found := cfgmap[CtrlMapKey]; found {
if submap, ok := value.(map[interface{}]interface{}); ok {
if value, found := submap["endpoint"]; found {
if value, found := submap[CtrlEndpointMapKey]; found {
address, err := transport.ParseAddress(value.(string))
if err != nil {
return nil, fmt.Errorf("cannot parse [ctrl/endpoint] (%s)", err)