387 lines
7.7 KiB
Go
387 lines
7.7 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
package outil
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/elastic/beats/libbeat/beat"
|
|
"github.com/elastic/beats/libbeat/common"
|
|
"github.com/elastic/beats/libbeat/common/fmtstr"
|
|
"github.com/elastic/beats/libbeat/conditions"
|
|
)
|
|
|
|
type Selector struct {
|
|
sel SelectorExpr
|
|
}
|
|
|
|
type Settings struct {
|
|
// single selector key and default option keyword
|
|
Key string
|
|
|
|
// multi-selector key in config
|
|
MultiKey string
|
|
|
|
// if enabled a selector `key` in config will be generated, if `key` is present
|
|
EnableSingleOnly bool
|
|
|
|
// Fail building selector if `key` and `multiKey` are missing
|
|
FailEmpty bool
|
|
}
|
|
|
|
type SelectorExpr interface {
|
|
sel(evt *beat.Event) (string, error)
|
|
}
|
|
|
|
type emptySelector struct{}
|
|
|
|
type listSelector struct {
|
|
selectors []SelectorExpr
|
|
}
|
|
|
|
type condSelector struct {
|
|
s SelectorExpr
|
|
cond conditions.Condition
|
|
}
|
|
|
|
type constSelector struct {
|
|
s string
|
|
}
|
|
|
|
type fmtSelector struct {
|
|
f fmtstr.EventFormatString
|
|
otherwise string
|
|
}
|
|
|
|
type mapSelector struct {
|
|
from SelectorExpr
|
|
otherwise string
|
|
to map[string]string
|
|
}
|
|
|
|
var nilSelector SelectorExpr = &emptySelector{}
|
|
|
|
func MakeSelector(es ...SelectorExpr) Selector {
|
|
switch len(es) {
|
|
case 0:
|
|
return Selector{nilSelector}
|
|
case 1:
|
|
return Selector{es[0]}
|
|
default:
|
|
return Selector{ConcatSelectorExpr(es...)}
|
|
}
|
|
}
|
|
|
|
// Select runs configured selector against the current event.
|
|
// If no matching selector is found, an empty string is returned.
|
|
// It's up to the caller to decide if an empty string is an error
|
|
// or an expected result.
|
|
func (s Selector) Select(evt *beat.Event) (string, error) {
|
|
return s.sel.sel(evt)
|
|
}
|
|
|
|
func (s Selector) IsEmpty() bool {
|
|
return s.sel == nilSelector || s.sel == nil
|
|
}
|
|
|
|
func (s Selector) IsConst() bool {
|
|
if s.sel == nilSelector {
|
|
return true
|
|
}
|
|
|
|
_, ok := s.sel.(*constSelector)
|
|
return ok
|
|
}
|
|
|
|
func BuildSelectorFromConfig(
|
|
cfg *common.Config,
|
|
settings Settings,
|
|
) (Selector, error) {
|
|
var sel []SelectorExpr
|
|
|
|
key := settings.Key
|
|
multiKey := settings.MultiKey
|
|
found := false
|
|
|
|
if cfg.HasField(multiKey) {
|
|
found = true
|
|
sub, err := cfg.Child(multiKey, -1)
|
|
if err != nil {
|
|
return Selector{}, err
|
|
}
|
|
|
|
var table []*common.Config
|
|
if err := sub.Unpack(&table); err != nil {
|
|
return Selector{}, err
|
|
}
|
|
|
|
for _, config := range table {
|
|
action, err := buildSingle(config, key)
|
|
if err != nil {
|
|
return Selector{}, err
|
|
}
|
|
|
|
if action != nilSelector {
|
|
sel = append(sel, action)
|
|
}
|
|
}
|
|
}
|
|
|
|
if settings.EnableSingleOnly && cfg.HasField(key) {
|
|
found = true
|
|
|
|
// expect event-format-string
|
|
str, err := cfg.String(key, -1)
|
|
if err != nil {
|
|
return Selector{}, err
|
|
}
|
|
|
|
fmtstr, err := fmtstr.CompileEvent(str)
|
|
if err != nil {
|
|
return Selector{}, fmt.Errorf("%v in %v", err, cfg.PathOf(key))
|
|
}
|
|
|
|
if fmtstr.IsConst() {
|
|
str, err := fmtstr.Run(nil)
|
|
if err != nil {
|
|
return Selector{}, err
|
|
}
|
|
|
|
if str != "" {
|
|
sel = append(sel, ConstSelectorExpr(str))
|
|
}
|
|
} else {
|
|
sel = append(sel, FmtSelectorExpr(fmtstr, ""))
|
|
}
|
|
}
|
|
|
|
if settings.FailEmpty && !found {
|
|
if settings.EnableSingleOnly {
|
|
return Selector{}, fmt.Errorf("missing required '%v' or '%v' in %v",
|
|
key, multiKey, cfg.Path())
|
|
}
|
|
|
|
return Selector{}, fmt.Errorf("missing required '%v' in %v",
|
|
multiKey, cfg.Path())
|
|
}
|
|
|
|
return MakeSelector(sel...), nil
|
|
}
|
|
|
|
func EmptySelectorExpr() SelectorExpr {
|
|
return nilSelector
|
|
}
|
|
|
|
func ConstSelectorExpr(s string) SelectorExpr {
|
|
return &constSelector{s}
|
|
}
|
|
|
|
func FmtSelectorExpr(fmt *fmtstr.EventFormatString, fallback string) SelectorExpr {
|
|
return &fmtSelector{*fmt, fallback}
|
|
}
|
|
|
|
func ConcatSelectorExpr(s ...SelectorExpr) SelectorExpr {
|
|
return &listSelector{s}
|
|
}
|
|
|
|
func ConditionalSelectorExpr(
|
|
s SelectorExpr,
|
|
cond conditions.Condition,
|
|
) SelectorExpr {
|
|
return &condSelector{s, cond}
|
|
}
|
|
|
|
func LookupSelectorExpr(
|
|
s SelectorExpr,
|
|
table map[string]string,
|
|
fallback string,
|
|
) SelectorExpr {
|
|
return &mapSelector{s, fallback, table}
|
|
}
|
|
|
|
func buildSingle(cfg *common.Config, key string) (SelectorExpr, error) {
|
|
// TODO: check for unknown fields
|
|
|
|
// 1. extract required key-word handler
|
|
if !cfg.HasField(key) {
|
|
return nil, fmt.Errorf("missing %v", cfg.PathOf(key))
|
|
}
|
|
|
|
str, err := cfg.String(key, -1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
evtfmt, err := fmtstr.CompileEvent(str)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%v in %v", err, cfg.PathOf(key))
|
|
}
|
|
|
|
// 2. extract optional `default` value
|
|
var otherwise string
|
|
if cfg.HasField("default") {
|
|
tmp, err := cfg.String("default", -1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
otherwise = tmp
|
|
}
|
|
|
|
// 3. extract optional `mapping`
|
|
mapping := struct {
|
|
Table map[string]string `config:"mappings"`
|
|
}{nil}
|
|
if cfg.HasField("mappings") {
|
|
if err := cfg.Unpack(&mapping); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// 4. extract conditional
|
|
var cond conditions.Condition
|
|
if cfg.HasField("when") {
|
|
sub, err := cfg.Child("when", -1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
condConfig := conditions.Config{}
|
|
if err := sub.Unpack(&condConfig); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tmp, err := conditions.NewCondition(&condConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
cond = tmp
|
|
}
|
|
|
|
// 5. build selector from available fields
|
|
var sel SelectorExpr
|
|
if len(mapping.Table) > 0 {
|
|
if evtfmt.IsConst() {
|
|
str, err := evtfmt.Run(nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
str = mapping.Table[str]
|
|
if str == "" {
|
|
str = otherwise
|
|
}
|
|
|
|
if str == "" {
|
|
sel = nilSelector
|
|
} else {
|
|
sel = ConstSelectorExpr(str)
|
|
}
|
|
} else {
|
|
sel = &mapSelector{
|
|
from: FmtSelectorExpr(evtfmt, ""),
|
|
to: mapping.Table,
|
|
otherwise: otherwise,
|
|
}
|
|
}
|
|
} else {
|
|
if evtfmt.IsConst() {
|
|
str, err := evtfmt.Run(nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if str == "" {
|
|
sel = nilSelector
|
|
} else {
|
|
sel = ConstSelectorExpr(str)
|
|
}
|
|
} else {
|
|
sel = FmtSelectorExpr(evtfmt, otherwise)
|
|
}
|
|
}
|
|
if cond != nil && sel != nilSelector {
|
|
sel = ConditionalSelectorExpr(sel, cond)
|
|
}
|
|
|
|
return sel, nil
|
|
}
|
|
|
|
func (s *emptySelector) sel(evt *beat.Event) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (s *listSelector) sel(evt *beat.Event) (string, error) {
|
|
for _, sub := range s.selectors {
|
|
n, err := sub.sel(evt)
|
|
if err != nil { // TODO: try
|
|
return n, err
|
|
}
|
|
|
|
if n != "" {
|
|
return n, nil
|
|
}
|
|
}
|
|
|
|
return "", nil
|
|
}
|
|
|
|
func (s *condSelector) sel(evt *beat.Event) (string, error) {
|
|
if !s.cond.Check(evt) {
|
|
return "", nil
|
|
}
|
|
return s.s.sel(evt)
|
|
}
|
|
|
|
func (s *constSelector) sel(_ *beat.Event) (string, error) {
|
|
return s.s, nil
|
|
}
|
|
|
|
func (s *fmtSelector) sel(evt *beat.Event) (string, error) {
|
|
n, err := s.f.Run(evt)
|
|
if err != nil {
|
|
// err will be set if not all keys present in event ->
|
|
// return empty selector result and ignore error
|
|
return s.otherwise, nil
|
|
}
|
|
|
|
if n == "" {
|
|
return s.otherwise, nil
|
|
}
|
|
return n, nil
|
|
}
|
|
|
|
func (s *mapSelector) sel(evt *beat.Event) (string, error) {
|
|
n, err := s.from.sel(evt)
|
|
if err != nil {
|
|
if s.otherwise == "" {
|
|
return "", err
|
|
}
|
|
return s.otherwise, nil
|
|
}
|
|
|
|
if n == "" {
|
|
return s.otherwise, nil
|
|
}
|
|
|
|
n = s.to[n]
|
|
if n == "" {
|
|
return s.otherwise, nil
|
|
}
|
|
return n, nil
|
|
}
|