diff --git a/parser/helpers.go b/parser/helpers.go index e34013b1..7f2f539b 100644 --- a/parser/helpers.go +++ b/parser/helpers.go @@ -185,11 +185,7 @@ func (cfr *ConfigurationFileReplacement) setValueWithSjson(jsonStr string, path } setValue = value default: - if v, err := strconv.Atoi(value); err == nil { - setValue = v - } else { - setValue = value - } + setValue = value } } @@ -208,29 +204,30 @@ func (f *ConfigurationFile) LookupConfigurationValue(cfr ConfigurationFileReplac // 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 // daemon configuration here. - huntPath := configMatchRegex.ReplaceAllString( - configMatchRegex.FindString(cfr.ReplaceWith.String()), "$1", - ) - - var path []string - for _, value := range strings.Split(huntPath, ".") { - path = append(path, strcase.ToSnake(value)) - } - - // Look for the key in the configuration file, and if found return that value to the - // calling function. - match, _, _, err := jsonparser.Get(f.configuration, path...) - if err != nil { - if err != jsonparser.KeyPathNotFoundError { - return string(match), err + var lookupErr error + result := configMatchRegex.ReplaceAllStringFunc(cfr.ReplaceWith.String(), func(placeholder string) string { + if lookupErr != nil { + return placeholder } + keyPath := configMatchRegex.ReplaceAllString(placeholder, "$1") - log.WithFields(log.Fields{"path": path, "filename": f.FileName}).Debug("attempted to load a configuration value that does not exist") + var path []string + for _, part := range strings.Split(keyPath, ".") { + path = append(path, strcase.ToSnake(part)) + } - // If there is no key, keep the original value intact, that way it is obvious there - // is a replace issue at play. - return string(match), nil - } else { - return configMatchRegex.ReplaceAllString(cfr.ReplaceWith.String(), string(match)), nil - } + // Look for the key in the Wings configuration and substitute the placeholder. + match, _, _, err := jsonparser.Get(f.configuration, path...) + if err != nil { + if err != jsonparser.KeyPathNotFoundError { + lookupErr = err + return placeholder + } + log.WithFields(log.Fields{"path": path, "filename": f.FileName}).Debug("attempted to load a configuration value that does not exist") + // Leave placeholder intact so the misconfiguration is visible. + return placeholder + } + return string(match) + }) + return result, lookupErr } diff --git a/parser/parser.go b/parser/parser.go index a0482d86..cd394c67 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -128,18 +128,27 @@ func (f *ConfigurationFile) UnmarshalJSON(data []byte) error { return err } - if err := json.Unmarshal(*m["file"], &f.FileName); err != nil { + fileRaw, ok := m["file"] + if !ok || fileRaw == nil { + return errors.New("parser: configuration file missing required 'file' key") + } + if err := json.Unmarshal(*fileRaw, &f.FileName); err != nil { return err } - if err := json.Unmarshal(*m["parser"], &f.Parser); err != nil { + parserRaw, ok := m["parser"] + if !ok || parserRaw == nil { + return errors.New("parser: configuration file missing required 'parser' key") + } + if err := json.Unmarshal(*parserRaw, &f.Parser); err != nil { return err } - if err := json.Unmarshal(*m["replace"], &f.Replace); err != nil { - log.WithField("file", f.FileName).WithField("error", err).Warn("failed to unmarshal configuration file replacement") - - f.Replace = []ConfigurationFileReplacement{} + f.Replace = []ConfigurationFileReplacement{} + if replaceRaw, ok := m["replace"]; ok && replaceRaw != nil { + if err := json.Unmarshal(*replaceRaw, &f.Replace); err != nil { + log.WithField("file", f.FileName).WithField("error", err).Warn("failed to unmarshal configuration file replacement") + } } // test if "create_file" exists, if not just assume true @@ -292,6 +301,7 @@ func (f *ConfigurationFile) parseXmlFile(file ufs.File) error { k := xmlValueMatchRegex.ReplaceAllString(value, "$1") v := xmlValueMatchRegex.ReplaceAllString(value, "$2") + element.RemoveAttr(k) element.CreateAttr(k, v) } else { element.SetText(value) @@ -462,9 +472,12 @@ func (f *ConfigurationFile) parseYamlFile(file ufs.File) error { } var jsonData interface{} - if err := json.Unmarshal(data, &jsonData); err != nil { + yamlDecoder := json.NewDecoder(bytes.NewReader(data)) + yamlDecoder.UseNumber() + if err := yamlDecoder.Decode(&jsonData); err != nil { return err } + jsonData = normalizeYamlTypes(jsonData) marshaled, err := yaml.Marshal(jsonData) if err != nil { @@ -536,6 +549,43 @@ func (f *ConfigurationFile) parseTomlFile(file ufs.File) error { return nil } +// normalizeYamlTypes converts json.Number values (produced by UseNumber()) into +// proper Go numeric types so that yaml.Marshal writes integers as plain integers +// instead of float64 scientific notation (e.g. 1.5e+18 for large Snowflake IDs). +func normalizeYamlTypes(value interface{}) interface{} { + switch typed := value.(type) { + case map[string]interface{}: + for key, item := range typed { + typed[key] = normalizeYamlTypes(item) + } + return typed + case []interface{}: + for i := range typed { + typed[i] = normalizeYamlTypes(typed[i]) + } + return typed + case json.Number: + s := typed.String() + // Preserve float representation: if the number contains '.', 'e' or 'E' + // it was originally a float and must not be coerced to int64. + if strings.ContainsAny(s, ".eE") { + if floatVal, err := typed.Float64(); err == nil { + return floatVal + } + return s + } + if intVal, err := typed.Int64(); err == nil { + return intVal + } + if floatVal, err := typed.Float64(); err == nil { + return floatVal + } + return s + default: + return value + } +} + func normalizeTomlTypes(value interface{}) interface{} { switch typed := value.(type) { case map[string]interface{}: @@ -585,6 +635,14 @@ func (f *ConfigurationFile) parseTextFile(file ufs.File) error { if !bytes.HasPrefix(line, []byte(replace.Match)) { continue } + // If an if_value is set, only replace when the remainder of the line matches. + // Trim trailing \r\n so Windows line endings do not break the comparison. + if replace.IfValue != "" { + remainder := bytes.TrimRight(bytes.TrimPrefix(line, []byte(replace.Match)), "\r\n") + if string(remainder) != replace.IfValue { + continue + } + } b.Write(replace.ReplaceWith.Bytes()) replaced = true }