Try to parse arrays more correctly; not quite working just yet but the concept seems to work
This commit is contained in:
parent
fff9a89ebb
commit
c19fc25882
|
@ -76,13 +76,13 @@ func (cfr *ConfigurationFileReplacement) getKeyValue(value []byte) interface{} {
|
||||||
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error) {
|
||||||
parsed, err := gabs.ParseJSON(data)
|
parsed, err := gabs.ParseJSON(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range f.Replace {
|
for _, v := range f.Replace {
|
||||||
value, err := f.LookupConfigurationValue(v)
|
value, err := f.LookupConfigurationValue(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a wildcard character, and if found split the key on that value to
|
// Check for a wildcard character, and if found split the key on that value to
|
||||||
|
@ -97,12 +97,20 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
||||||
// time this code is being written.
|
// time this code is being written.
|
||||||
for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() {
|
for _, child := range parsed.Path(strings.Trim(parts[0], ".")).Children() {
|
||||||
if err := v.SetAtPathway(child, strings.Trim(parts[1], "."), []byte(value)); err != nil {
|
if err := v.SetAtPathway(child, strings.Trim(parts[1], "."), []byte(value)); err != nil {
|
||||||
return nil, err
|
if errors.Is(err, gabs.ErrNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Wrap(err, "failed to set config value of array child")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = v.SetAtPathway(parsed, v.Match, []byte(value)); err != nil {
|
if err = v.SetAtPathway(parsed, v.Match, []byte(value)); err != nil {
|
||||||
return nil, err
|
if errors.Is(err, gabs.ErrNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.Wrap(err, "unable to set config value at pathway: "+v.Match)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -110,42 +118,85 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
||||||
return parsed, nil
|
return parsed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sets the value at a specific pathway, but checks if we were looking for a specific
|
// Regex used to check if there is an array element present in the given pathway by looking for something
|
||||||
// value or not before doing it.
|
// along the lines of "something[1]" or "something[1].nestedvalue" as the path.
|
||||||
func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path string, value []byte) error {
|
var checkForArrayElement = regexp.MustCompile(`^([^\[\]]+)\[([\d]+)](\..+)?$`)
|
||||||
if cfr.IfValue != "" {
|
|
||||||
// If this is a regex based matching, we need to get a little more creative since
|
|
||||||
// we're only going to replacing part of the string, and not the whole thing.
|
|
||||||
if c.Exists(path) && strings.HasPrefix(cfr.IfValue, "regex:") {
|
|
||||||
// We're doing some regex here.
|
|
||||||
r, err := regexp.Compile(strings.TrimPrefix(cfr.IfValue, "regex:"))
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(log.Fields{"if_value": strings.TrimPrefix(cfr.IfValue, "regex:"), "error": err}).
|
|
||||||
Warn("configuration if_value using invalid regexp, cannot perform replacement")
|
|
||||||
|
|
||||||
return nil
|
// Attempt to set the value of the path depending on if it is an array or not. Gabs cannot handle array
|
||||||
}
|
// values as "something[1]" but can parse them just fine. This is basically just overly complex code
|
||||||
|
// to handle that edge case and ensure the value gets set correctly.
|
||||||
|
func setValueAtPath(c *gabs.Container, path string, value interface{}) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
// If the path exists and there is a regex match, go ahead and attempt the replacement
|
matches := checkForArrayElement.FindStringSubmatch(path)
|
||||||
// using the value we got from the key. This will only replace the one match.
|
if len(matches) < 3 {
|
||||||
v := strings.Trim(string(c.Path(path).Bytes()), "\"")
|
// Only update the value if the pathway actually exists in the configuration, otherwise
|
||||||
if r.Match([]byte(v)) {
|
// do nothing.
|
||||||
_, err := c.SetP(r.ReplaceAllString(v, string(value)), path)
|
if c.ExistsP(path) {
|
||||||
|
_, err = c.SetP(value, path)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
i, _ := strconv.Atoi(matches[2])
|
||||||
} else {
|
// Find the array element "i" or abort if it does not exist.
|
||||||
if !c.Exists(path) || (c.Exists(path) && !bytes.Equal(c.Bytes(), []byte(cfr.IfValue))) {
|
ct, err := c.ArrayElementP(i, matches[1])
|
||||||
return nil
|
if err != nil {
|
||||||
}
|
return errors.Wrap(err, "error while parsing array element at path")
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there are four matches in the regex it means that we managed to also match a trailing pathway
|
||||||
|
// for the key, which should be found in the given array key item and modified further.
|
||||||
|
if len(matches) == 4 {
|
||||||
|
ct, err = ct.ArrayElementP(i, strings.TrimPrefix(matches[3], "."))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "error while parsing array element as nested child")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := c.SetP(cfr.getKeyValue(value), path)
|
// Try to set the value. If the path does not exist an error will be raised to the caller which will
|
||||||
|
// then check if the error is because the path is missing. In those cases we just ignore the error since
|
||||||
|
// we don't want to do anything specifically when that happens.
|
||||||
|
if _, err = ct.Set(value); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to set value at config path: "+path)
|
||||||
|
}
|
||||||
|
|
||||||
return err
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the value at a specific pathway, but checks if we were looking for a specific
|
||||||
|
// value or not before doing it.
|
||||||
|
func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path string, value []byte) error {
|
||||||
|
if cfr.IfValue == "" {
|
||||||
|
return setValueAtPath(c, path, cfr.getKeyValue(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is a regex based matching, we need to get a little more creative since
|
||||||
|
// we're only going to replacing part of the string, and not the whole thing.
|
||||||
|
if c.ExistsP(path) && strings.HasPrefix(cfr.IfValue, "regex:") {
|
||||||
|
// We're doing some regex here.
|
||||||
|
r, err := regexp.Compile(strings.TrimPrefix(cfr.IfValue, "regex:"))
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{"if_value": strings.TrimPrefix(cfr.IfValue, "regex:"), "error": err}).
|
||||||
|
Warn("configuration if_value using invalid regexp, cannot perform replacement")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the path exists and there is a regex match, go ahead and attempt the replacement
|
||||||
|
// using the value we got from the key. This will only replace the one match.
|
||||||
|
v := strings.Trim(string(c.Path(path).Bytes()), "\"")
|
||||||
|
if r.Match([]byte(v)) {
|
||||||
|
return setValueAtPath(c, path, r.ReplaceAllString(v, string(value)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
} else if !c.ExistsP(path) || (c.ExistsP(path) && !bytes.Equal(c.Bytes(), []byte(cfr.IfValue))) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return setValueAtPath(c, path, cfr.getKeyValue(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Looks up a configuration value on the Daemon given a dot-notated syntax.
|
// Looks up a configuration value on the Daemon given a dot-notated syntax.
|
||||||
|
|
|
@ -96,8 +96,7 @@ func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// See comment on the replacement regex to understand what exactly this is doing.
|
cfr.Match = m
|
||||||
cfr.Match = cfrMatchReplacement.ReplaceAllString(m, ".$1")
|
|
||||||
|
|
||||||
iv, err := jsonparser.GetString(data, "if_value")
|
iv, err := jsonparser.GetString(data, "if_value")
|
||||||
// We only check keypath here since match & replace_with should be present on all of
|
// We only check keypath here since match & replace_with should be present on all of
|
||||||
|
@ -349,12 +348,12 @@ func (f *ConfigurationFile) parseJsonFile(path string) error {
|
||||||
func (f *ConfigurationFile) parseYamlFile(path string) error {
|
func (f *ConfigurationFile) parseYamlFile(path string) error {
|
||||||
b, err := readFileBytes(path)
|
b, err := readFileBytes(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
i := make(map[string]interface{})
|
i := make(map[string]interface{})
|
||||||
if err := yaml.Unmarshal(b, &i); err != nil {
|
if err := yaml.Unmarshal(b, &i); err != nil {
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unmarshal the yaml data into a JSON interface such that we can work with
|
// Unmarshal the yaml data into a JSON interface such that we can work with
|
||||||
|
@ -362,20 +361,20 @@ func (f *ConfigurationFile) parseYamlFile(path string) error {
|
||||||
// makes working with unknown JSON significantly easier.
|
// makes working with unknown JSON significantly easier.
|
||||||
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
|
jsonBytes, err := json.Marshal(dyno.ConvertMapI2MapS(i))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that the data is converted, treat it just like JSON and pass it to the
|
// Now that the data is converted, treat it just like JSON and pass it to the
|
||||||
// iterator function to update values as necessary.
|
// iterator function to update values as necessary.
|
||||||
data, err := f.IterateOverJson(jsonBytes)
|
data, err := f.IterateOverJson(jsonBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remarshal the JSON into YAML format before saving it back to the disk.
|
// Remarshal the JSON into YAML format before saving it back to the disk.
|
||||||
marshaled, err := yaml.Marshal(data.Data())
|
marshaled, err := yaml.Marshal(data.Data())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ioutil.WriteFile(path, marshaled, 0644)
|
return ioutil.WriteFile(path, marshaled, 0644)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user