Fix support for configuration files with more complex cases
This commit is contained in:
parent
a4c9562e42
commit
9de094f078
|
@ -1,12 +1,15 @@
|
||||||
package parser
|
package parser
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"github.com/Jeffail/gabs/v2"
|
"github.com/Jeffail/gabs/v2"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/iancoleman/strcase"
|
"github.com/iancoleman/strcase"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.uber.org/zap"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -44,32 +47,19 @@ func readFileBytes(path string) ([]byte, error) {
|
||||||
return ioutil.ReadAll(file)
|
return ioutil.ReadAll(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to set the value of the JSON key item based on the jsonparser value
|
|
||||||
// type returned.
|
|
||||||
func setPathway(c *gabs.Container, path string, value []byte, vt jsonparser.ValueType) error {
|
|
||||||
v := getKeyValue(value, vt)
|
|
||||||
|
|
||||||
_, err := c.SetP(v, path)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the value of a key based on the value type defined.
|
// Gets the value of a key based on the value type defined.
|
||||||
func getKeyValue(value []byte, vt jsonparser.ValueType) interface{} {
|
func getKeyValue(value []byte) interface{} {
|
||||||
switch vt {
|
if reflect.ValueOf(value).Kind() == reflect.Bool {
|
||||||
case jsonparser.Number:
|
|
||||||
{
|
|
||||||
v, _ := strconv.Atoi(string(value))
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
case jsonparser.Boolean:
|
|
||||||
{
|
|
||||||
v, _ := strconv.ParseBool(string(value))
|
v, _ := strconv.ParseBool(string(value))
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
return string(value)
|
// Try to parse into an int, if this fails just ignore the error and
|
||||||
|
if v, err := strconv.Atoi(string(value)); err == nil {
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return string(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over an unstructured JSON/YAML/etc. interface and set all of the required
|
// Iterate over an unstructured JSON/YAML/etc. interface and set all of the required
|
||||||
|
@ -88,7 +78,7 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range f.Replace {
|
for _, v := range f.Replace {
|
||||||
value, dt, err := f.LookupConfigurationValue(v)
|
value, err := f.LookupConfigurationValue(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -104,12 +94,12 @@ func (f *ConfigurationFile) IterateOverJson(data []byte) (*gabs.Container, error
|
||||||
// If the child is a null value, nothing will happen. Seems reasonable as of the
|
// If the child is a null value, nothing will happen. Seems reasonable as of the
|
||||||
// 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 := setPathway(child, strings.Trim(parts[1], "."), value, dt); err != nil {
|
if err := v.SetAtPathway(child, strings.Trim(parts[1], "."), value); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if err = setPathway(parsed, v.Match, value, dt); err != nil {
|
if err = v.SetAtPathway(parsed, v.Match, value); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,17 +108,34 @@ 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
|
||||||
|
// value or not before doing it.
|
||||||
|
func (cfr *ConfigurationFileReplacement) SetAtPathway(c *gabs.Container, path string, value []byte) error {
|
||||||
|
if cfr.IfValue != nil {
|
||||||
|
if !c.Exists(path) || (c.Exists(path) && !bytes.Equal(c.Bytes(), []byte(*cfr.IfValue))) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.SetP(getKeyValue(value), path)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplacement) ([]byte, jsonparser.ValueType, error) {
|
func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplacement) ([]byte, error) {
|
||||||
if !configMatchRegex.Match([]byte(cfr.Value)) {
|
// If this is not something that we can do a regex lookup on then just continue
|
||||||
return []byte(cfr.Value), cfr.ValueType, nil
|
// on our merry way. If the value isn't a string, we're not going to be doing anything
|
||||||
|
// with it anyways.
|
||||||
|
if cfr.ReplaceWith.Type() != jsonparser.String || !configMatchRegex.Match(cfr.ReplaceWith.Value()) {
|
||||||
|
return cfr.ReplaceWith.Value(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is a match, lookup the value in the configuration for the Daemon. If no key
|
// If there is a match, lookup the value in the configuration for the Daemon. If no key
|
||||||
// is found, just return the string representation, otherwise use the value from the
|
// is found, just return the string representation, otherwise use the value from the
|
||||||
// daemon configuration here.
|
// daemon configuration here.
|
||||||
huntPath := configMatchRegex.ReplaceAllString(
|
huntPath := configMatchRegex.ReplaceAllString(
|
||||||
configMatchRegex.FindString(cfr.Value), "$1",
|
configMatchRegex.FindString(cfr.ReplaceWith.String()), "$1",
|
||||||
)
|
)
|
||||||
|
|
||||||
var path []string
|
var path []string
|
||||||
|
@ -141,18 +148,24 @@ func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplac
|
||||||
|
|
||||||
// Look for the key in the configuration file, and if found return that value to the
|
// Look for the key in the configuration file, and if found return that value to the
|
||||||
// calling function.
|
// calling function.
|
||||||
match, dt, _, err := jsonparser.Get(f.configuration, path...)
|
match, _, _, err := jsonparser.Get(f.configuration, path...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != jsonparser.KeyPathNotFoundError {
|
if err != jsonparser.KeyPathNotFoundError {
|
||||||
return match, dt, errors.WithStack(err)
|
return match, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
zap.S().Warnw(
|
||||||
|
"attempted to load a configuration value that does not exist",
|
||||||
|
zap.Strings("path", path),
|
||||||
|
zap.String("filename", f.FileName),
|
||||||
|
)
|
||||||
|
|
||||||
// If there is no key, keep the original value intact, that way it is obvious there
|
// If there is no key, keep the original value intact, that way it is obvious there
|
||||||
// is a replace issue at play.
|
// is a replace issue at play.
|
||||||
return []byte(cfr.Value), cfr.ValueType, nil
|
return cfr.ReplaceWith.Value(), nil
|
||||||
} else {
|
} else {
|
||||||
replaced := []byte(configMatchRegex.ReplaceAllString(cfr.Value, string(match)))
|
replaced := []byte(configMatchRegex.ReplaceAllString(cfr.ReplaceWith.String(), string(match)))
|
||||||
|
|
||||||
return replaced, cfr.ValueType, nil
|
return replaced, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ package parser
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"github.com/beevik/etree"
|
"github.com/beevik/etree"
|
||||||
"github.com/buger/jsonparser"
|
"github.com/buger/jsonparser"
|
||||||
"github.com/ghodss/yaml"
|
"github.com/ghodss/yaml"
|
||||||
|
@ -44,10 +43,12 @@ type ConfigurationFile struct {
|
||||||
// Defines a single find/replace instance for a given server configuration file.
|
// Defines a single find/replace instance for a given server configuration file.
|
||||||
type ConfigurationFileReplacement struct {
|
type ConfigurationFileReplacement struct {
|
||||||
Match string `json:"match"`
|
Match string `json:"match"`
|
||||||
Value string `json:"value"`
|
IfValue *string `json:"if_value"`
|
||||||
ValueType jsonparser.ValueType `json:"-"`
|
ReplaceWith ReplaceValue `json:"replace_with"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles unmarshaling the JSON representation into a struct that provides more useful
|
||||||
|
// data to this functionality.
|
||||||
func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
||||||
if m, err := jsonparser.GetString(data, "match"); err != nil {
|
if m, err := jsonparser.GetString(data, "match"); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -55,17 +56,13 @@ func (cfr *ConfigurationFileReplacement) UnmarshalJSON(data []byte) error {
|
||||||
cfr.Match = m
|
cfr.Match = m
|
||||||
}
|
}
|
||||||
|
|
||||||
if v, dt, _, err := jsonparser.Get(data, "value"); err != nil {
|
if v, dt, _, err := jsonparser.Get(data, "replace_with"); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if dt != jsonparser.String && dt != jsonparser.Number && dt != jsonparser.Boolean {
|
cfr.ReplaceWith = ReplaceValue{
|
||||||
return errors.New(
|
value: v,
|
||||||
fmt.Sprintf("cannot parse JSON: received unexpected replacement value type: %s", dt.String()),
|
valueType: dt,
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cfr.Value = string(v)
|
|
||||||
cfr.ValueType = dt
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -139,7 +136,7 @@ func (f *ConfigurationFile) parseXmlFile(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, replacement := range f.Replace {
|
for i, replacement := range f.Replace {
|
||||||
value, _, err := f.LookupConfigurationValue(replacement)
|
value, err := f.LookupConfigurationValue(replacement)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -209,7 +206,7 @@ func (f *ConfigurationFile) parseXmlFile(path string) error {
|
||||||
func (f *ConfigurationFile) parseIniFile(path string) error {
|
func (f *ConfigurationFile) parseIniFile(path string) error {
|
||||||
// Ini package can't handle a non-existent file, so handle that automatically here
|
// Ini package can't handle a non-existent file, so handle that automatically here
|
||||||
// by creating it if not exists.
|
// by creating it if not exists.
|
||||||
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644);
|
file, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -223,7 +220,7 @@ func (f *ConfigurationFile) parseIniFile(path string) error {
|
||||||
for _, replacement := range f.Replace {
|
for _, replacement := range f.Replace {
|
||||||
path := strings.SplitN(replacement.Match, ".", 2)
|
path := strings.SplitN(replacement.Match, ".", 2)
|
||||||
|
|
||||||
value, _, err := f.LookupConfigurationValue(replacement)
|
value, err := f.LookupConfigurationValue(replacement)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -336,7 +333,7 @@ func (f *ConfigurationFile) parseTextFile(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
hasReplaced = true
|
hasReplaced = true
|
||||||
t = strings.Replace(t, replace.Match, replace.Value, 1)
|
t = strings.Replace(t, replace.Match, replace.ReplaceWith.String(), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there was a replacement that occurred on this specific line, do a write to the file
|
// If there was a replacement that occurred on this specific line, do a write to the file
|
||||||
|
@ -364,11 +361,19 @@ func (f *ConfigurationFile) parsePropertiesFile(path string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, replace := range f.Replace {
|
for _, replace := range f.Replace {
|
||||||
data, _, err := f.LookupConfigurationValue(replace)
|
data, err := f.LookupConfigurationValue(replace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
v, ok := p.Get(replace.Match)
|
||||||
|
// Don't attempt to replace the value if we're looking for a specific value and
|
||||||
|
// it does not match. If there was no match at all in the file for this key but
|
||||||
|
// we're doing an IfValue match, do nothing.
|
||||||
|
if replace.IfValue != nil && (!ok || (ok && v != *replace.IfValue)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if _, _, err := p.Set(replace.Match, string(data)); err != nil {
|
if _, _, err := p.Set(replace.Match, string(data)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
22
parser/value.go
Normal file
22
parser/value.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/buger/jsonparser"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReplaceValue struct {
|
||||||
|
value []byte
|
||||||
|
valueType jsonparser.ValueType `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *ReplaceValue) Value() []byte {
|
||||||
|
return cv.value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *ReplaceValue) String() string {
|
||||||
|
return string(cv.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cv *ReplaceValue) Type() jsonparser.ValueType {
|
||||||
|
return cv.valueType
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user