mirror of
https://github.com/Icinga/icingabeat.git
synced 2025-08-15 14:58:08 +02:00
386 lines
9.3 KiB
Go
386 lines
9.3 KiB
Go
package common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Event metadata constants. These keys are used within libbeat to identify
|
|
// metadata stored in an event.
|
|
const (
|
|
FieldsKey = "fields"
|
|
TagsKey = "tags"
|
|
)
|
|
|
|
var (
|
|
// ErrKeyNotFound indicates that the specified key was not found.
|
|
ErrKeyNotFound = errors.New("key not found")
|
|
)
|
|
|
|
// EventMetadata contains fields and tags that can be added to an event via
|
|
// configuration.
|
|
type EventMetadata struct {
|
|
Fields MapStr
|
|
FieldsUnderRoot bool `config:"fields_under_root"`
|
|
Tags []string
|
|
}
|
|
|
|
// MapStr is a map[string]interface{} wrapper with utility methods for common
|
|
// map operations like converting to JSON.
|
|
type MapStr map[string]interface{}
|
|
|
|
// Update copies all the key-value pairs from d to this map. If the key
|
|
// already exists then it is overwritten. This method does not merge nested
|
|
// maps.
|
|
func (m MapStr) Update(d MapStr) {
|
|
for k, v := range d {
|
|
m[k] = v
|
|
}
|
|
}
|
|
|
|
// DeepUpdate recursively copies the key-value pairs from d to this map.
|
|
// If the key is present and a map as well, the sub-map will be updated recursively
|
|
// via DeepUpdate.
|
|
func (m MapStr) DeepUpdate(d MapStr) {
|
|
for k, v := range d {
|
|
switch val := v.(type) {
|
|
case map[string]interface{}:
|
|
m[k] = deepUpdateValue(m[k], MapStr(val))
|
|
case MapStr:
|
|
m[k] = deepUpdateValue(m[k], val)
|
|
default:
|
|
m[k] = v
|
|
}
|
|
}
|
|
}
|
|
|
|
func deepUpdateValue(old interface{}, val MapStr) interface{} {
|
|
if old == nil {
|
|
return val
|
|
}
|
|
|
|
switch sub := old.(type) {
|
|
case MapStr:
|
|
sub.DeepUpdate(val)
|
|
return sub
|
|
case map[string]interface{}:
|
|
tmp := MapStr(sub)
|
|
tmp.DeepUpdate(val)
|
|
return tmp
|
|
default:
|
|
return val
|
|
}
|
|
}
|
|
|
|
// Delete deletes the given key from the map.
|
|
func (m MapStr) Delete(key string) error {
|
|
_, err := walkMap(key, m, opDelete)
|
|
return err
|
|
}
|
|
|
|
// CopyFieldsTo copies the field specified by key to the given map. It will
|
|
// overwrite the key if it exists. An error is returned if the key does not
|
|
// exist in the source map.
|
|
func (m MapStr) CopyFieldsTo(to MapStr, key string) error {
|
|
v, err := walkMap(key, m, opGet)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = walkMap(key, to, mapStrOperation{putOperation{v}, true})
|
|
return err
|
|
}
|
|
|
|
// Clone returns a copy of the MapStr. It recursively makes copies of inner
|
|
// maps.
|
|
func (m MapStr) Clone() MapStr {
|
|
result := MapStr{}
|
|
|
|
for k, v := range m {
|
|
if innerMap, ok := tryToMapStr(v); ok {
|
|
v = innerMap.Clone()
|
|
}
|
|
result[k] = v
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// HasKey returns true if the key exist. If an error occurs then false is
|
|
// returned with a non-nil error.
|
|
func (m MapStr) HasKey(key string) (bool, error) {
|
|
hasKey, err := walkMap(key, m, opHasKey)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return hasKey.(bool), nil
|
|
}
|
|
|
|
// GetValue gets a value from the map. If the key does not exist then an error
|
|
// is returned.
|
|
func (m MapStr) GetValue(key string) (interface{}, error) {
|
|
return walkMap(key, m, opGet)
|
|
}
|
|
|
|
// Put associates the specified value with the specified key. If the map
|
|
// previously contained a mapping for the key, the old value is replaced and
|
|
// returned. The key can be expressed in dot-notation (e.g. x.y) to put a value
|
|
// into a nested map.
|
|
//
|
|
// If you need insert keys containing dots then you must use bracket notation
|
|
// to insert values (e.g. m[key] = value).
|
|
func (m MapStr) Put(key string, value interface{}) (interface{}, error) {
|
|
return walkMap(key, m, mapStrOperation{putOperation{value}, true})
|
|
}
|
|
|
|
// StringToPrint returns the MapStr as pretty JSON.
|
|
func (m MapStr) StringToPrint() string {
|
|
json, err := json.MarshalIndent(m, "", " ")
|
|
if err != nil {
|
|
return fmt.Sprintf("Not valid json: %v", err)
|
|
}
|
|
return string(json)
|
|
}
|
|
|
|
// String returns the MapStr as JSON.
|
|
func (m MapStr) String() string {
|
|
bytes, err := json.Marshal(m)
|
|
if err != nil {
|
|
return fmt.Sprintf("Not valid json: %v", err)
|
|
}
|
|
return string(bytes)
|
|
}
|
|
|
|
// Flatten flattens the given MapStr and returns a flat MapStr.
|
|
//
|
|
// Example:
|
|
// "hello": MapStr{"world": "test" }
|
|
//
|
|
// This is converted to:
|
|
// "hello.world": "test"
|
|
//
|
|
// This can be useful for testing or logging.
|
|
func (m MapStr) Flatten() MapStr {
|
|
return flatten("", m, MapStr{})
|
|
}
|
|
|
|
// flatten is a helper for Flatten. See docs for Flatten. For convenience the
|
|
// out parameter is returned.
|
|
func flatten(prefix string, in, out MapStr) MapStr {
|
|
for k, v := range in {
|
|
var fullKey string
|
|
if prefix == "" {
|
|
fullKey = k
|
|
} else {
|
|
fullKey = fmt.Sprintf("%s.%s", prefix, k)
|
|
}
|
|
|
|
if m, ok := tryToMapStr(v); ok {
|
|
flatten(fullKey, m, out)
|
|
} else {
|
|
out[fullKey] = v
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// MapStrUnion creates a new MapStr containing the union of the
|
|
// key-value pairs of the two maps. If the same key is present in
|
|
// both, the key-value pairs from dict2 overwrite the ones from dict1.
|
|
func MapStrUnion(dict1 MapStr, dict2 MapStr) MapStr {
|
|
dict := MapStr{}
|
|
|
|
for k, v := range dict1 {
|
|
dict[k] = v
|
|
}
|
|
|
|
for k, v := range dict2 {
|
|
dict[k] = v
|
|
}
|
|
return dict
|
|
}
|
|
|
|
// MergeFields merges the top-level keys and values in each source map (it does
|
|
// not perform a deep merge). If the same key exists in both, the value in
|
|
// fields takes precedence. If underRoot is true then the contents of the fields
|
|
// MapStr is merged with the value of the 'fields' key in ms.
|
|
//
|
|
// An error is returned if underRoot is true and the value of ms.fields is not a
|
|
// MapStr.
|
|
func MergeFields(ms, fields MapStr, underRoot bool) error {
|
|
if ms == nil || len(fields) == 0 {
|
|
return nil
|
|
}
|
|
|
|
fieldsMS := ms
|
|
if !underRoot {
|
|
f, ok := ms[FieldsKey]
|
|
if !ok {
|
|
fieldsMS = make(MapStr, len(fields))
|
|
ms[FieldsKey] = fieldsMS
|
|
} else {
|
|
// Use existing 'fields' value.
|
|
var err error
|
|
fieldsMS, err = toMapStr(f)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add fields and override.
|
|
for k, v := range fields {
|
|
fieldsMS[k] = v
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AddTags appends a tag to the tags field of ms. If the tags field does not
|
|
// exist then it will be created. If the tags field exists and is not a []string
|
|
// then an error will be returned. It does not deduplicate the list of tags.
|
|
func AddTags(ms MapStr, tags []string) error {
|
|
if ms == nil || len(tags) == 0 {
|
|
return nil
|
|
}
|
|
eventTags, exists := ms[TagsKey]
|
|
if !exists {
|
|
ms[TagsKey] = tags
|
|
return nil
|
|
}
|
|
|
|
switch arr := eventTags.(type) {
|
|
case []string:
|
|
ms[TagsKey] = append(arr, tags...)
|
|
case []interface{}:
|
|
for _, tag := range tags {
|
|
arr = append(arr, tag)
|
|
}
|
|
ms[TagsKey] = arr
|
|
default:
|
|
return errors.Errorf("expected string array by type is %T", eventTags)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// toMapStr performs a type assertion on v and returns a MapStr. v can be either
|
|
// a MapStr or a map[string]interface{}. If it's any other type or nil then
|
|
// an error is returned.
|
|
func toMapStr(v interface{}) (MapStr, error) {
|
|
m, ok := tryToMapStr(v)
|
|
if !ok {
|
|
return nil, errors.Errorf("expected map but type is %T", v)
|
|
}
|
|
return m, nil
|
|
}
|
|
|
|
func tryToMapStr(v interface{}) (MapStr, bool) {
|
|
switch m := v.(type) {
|
|
case MapStr:
|
|
return m, true
|
|
case map[string]interface{}:
|
|
return MapStr(m), true
|
|
default:
|
|
return nil, false
|
|
}
|
|
}
|
|
|
|
// walkMap walks the data MapStr to arrive at the value specified by the key.
|
|
// The key is expressed in dot-notation (eg. x.y.z). When the key is found then
|
|
// the given mapStrOperation is invoked.
|
|
func walkMap(key string, data MapStr, op mapStrOperation) (interface{}, error) {
|
|
var err error
|
|
keyParts := strings.Split(key, ".")
|
|
|
|
// Walk maps until reaching a leaf object.
|
|
m := data
|
|
for i, k := range keyParts[0 : len(keyParts)-1] {
|
|
v, exists := m[k]
|
|
if !exists {
|
|
if op.CreateMissingKeys {
|
|
newMap := MapStr{}
|
|
m[k] = newMap
|
|
m = newMap
|
|
continue
|
|
}
|
|
return nil, errors.Wrapf(ErrKeyNotFound, "key=%v", strings.Join(keyParts[0:i+1], "."))
|
|
}
|
|
|
|
m, err = toMapStr(v)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "key=%v", strings.Join(keyParts[0:i+1], "."))
|
|
}
|
|
}
|
|
|
|
// Execute the mapStrOperator on the leaf object.
|
|
v, err := op.Do(keyParts[len(keyParts)-1], m)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "key=%v", key)
|
|
}
|
|
|
|
return v, nil
|
|
}
|
|
|
|
// mapStrOperation types
|
|
|
|
// These are static mapStrOperation types that store no state and are reusable.
|
|
var (
|
|
opDelete = mapStrOperation{deleteOperation{}, false}
|
|
opGet = mapStrOperation{getOperation{}, false}
|
|
opHasKey = mapStrOperation{hasKeyOperation{}, false}
|
|
)
|
|
|
|
// mapStrOperation represents an operation that can be applied to map.
|
|
type mapStrOperation struct {
|
|
mapStrOperator
|
|
CreateMissingKeys bool
|
|
}
|
|
|
|
// mapStrOperator is an interface with a single function that performs an
|
|
// operation on a MapStr.
|
|
type mapStrOperator interface {
|
|
Do(key string, data MapStr) (value interface{}, err error)
|
|
}
|
|
|
|
type deleteOperation struct{}
|
|
|
|
func (op deleteOperation) Do(key string, data MapStr) (interface{}, error) {
|
|
value, found := data[key]
|
|
if !found {
|
|
return nil, ErrKeyNotFound
|
|
}
|
|
delete(data, key)
|
|
return value, nil
|
|
}
|
|
|
|
type getOperation struct{}
|
|
|
|
func (op getOperation) Do(key string, data MapStr) (interface{}, error) {
|
|
value, found := data[key]
|
|
if !found {
|
|
return nil, ErrKeyNotFound
|
|
}
|
|
return value, nil
|
|
}
|
|
|
|
type hasKeyOperation struct{}
|
|
|
|
func (op hasKeyOperation) Do(key string, data MapStr) (interface{}, error) {
|
|
_, found := data[key]
|
|
return found, nil
|
|
}
|
|
|
|
type putOperation struct {
|
|
Value interface{}
|
|
}
|
|
|
|
func (op putOperation) Do(key string, data MapStr) (interface{}, error) {
|
|
existingValue, _ := data[key]
|
|
data[key] = op.Value
|
|
return existingValue, nil
|
|
}
|