runtime: Scan drop-in directory, read files and decode them

updateFromDropIn() uses the infrastructure built by previous commits to
ensure no contents of 'tomlConfig' are lost during decoding.   To do
this, we preserve the current contents of our tomlConfig in a clone and
decode a drop-in into the original.  At this point, the original
instance is updated but its Agent and/or Hypervisor fields are
potentially damaged.

To merge, we update the clone's Agent/Hypervisor from the original
instance.   Now the clone has the desired Agent/Hypervisor and the
original instance has the rest, so to finish, we just need to move the
clone's Agent/Hypervisor to the original.

Signed-off-by: Pavel Mores <pmores@redhat.com>
This commit is contained in:
Pavel Mores 2022-04-14 19:29:41 +02:00
parent 2c1efcc697
commit 0f9856c465

View File

@ -10,6 +10,7 @@ package katautils
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"reflect"
@ -177,6 +178,20 @@ type agent struct {
DialTimeout uint32 `toml:"dial_timeout"`
}
func (orig *tomlConfig) Clone() tomlConfig {
clone := *orig
clone.Hypervisor = make(map[string]hypervisor)
clone.Agent = make(map[string]agent)
for key, value := range orig.Hypervisor {
clone.Hypervisor[key] = value
}
for key, value := range orig.Agent {
clone.Agent[key] = value
}
return clone
}
func (h hypervisor) path() (string, error) {
p := h.Path
@ -1309,6 +1324,70 @@ func decodeConfig(configPath string) (tomlConfig, string, error) {
return tomlConf, resolved, nil
}
func decodeDropIns(mainConfigPath string, tomlConf *tomlConfig) error {
configDir := filepath.Dir(mainConfigPath)
dropInDir := filepath.Join(configDir, "config.d")
files, err := ioutil.ReadDir(dropInDir)
if err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("error reading %q directory: %s", dropInDir, err)
} else {
return nil
}
}
for _, file := range files {
dropInFpath := filepath.Join(dropInDir, file.Name())
err = updateFromDropIn(dropInFpath, tomlConf)
if err != nil {
return err
}
}
return nil
}
func updateFromDropIn(dropInFpath string, tomlConf *tomlConfig) error {
configData, err := os.ReadFile(dropInFpath)
if err != nil {
return fmt.Errorf("error reading file %q: %s", dropInFpath, err)
}
// Ordinarily, BurntSushi only updates fields of tomlConfig that are
// changed by the file and leaves the rest alone. This doesn't apply
// though to tomlConfig substructures that are stored in maps. Their
// previous contents are erased by toml.Decode() and only fields changed by
// the file are set. To work around this, a bit of juggling is needed to
// preserve the previous contents and merge them manually with the incoming
// changes afterwards, using reflection.
tomlConfOrig := tomlConf.Clone()
var md toml.MetaData
md, err = toml.Decode(string(configData), &tomlConf)
if err != nil {
return fmt.Errorf("error decoding file %q: %s", dropInFpath, err)
}
if len(md.Undecoded()) > 0 {
msg := fmt.Sprintf("warning: undecoded keys in %q: %+v", dropInFpath, md.Undecoded())
kataUtilsLogger.Warn(msg)
}
for _, key := range md.Keys() {
err = applyKey(*tomlConf, key, &tomlConfOrig)
if err != nil {
return fmt.Errorf("error applying key '%+v' from drop-in file %q: %s", key, dropInFpath, err)
}
}
tomlConf.Hypervisor = tomlConfOrig.Hypervisor
tomlConf.Agent = tomlConfOrig.Agent
return nil
}
func applyKey(sourceConf tomlConfig, key []string, targetConf *tomlConfig) error {
// Any key that might need treatment provided by this function has to have