-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for namespacing processors on Register by using dots on namespace names. When constructing a processor from config, the namespace will check, only one valid plugin is requested. With multi-level namespaces, the `when`-conditional setting can be applied at any level (even level 0). That is the filter ``` processors: - drop_fields: fields: ['field'] when: ... ``` Can now be written as: ``` processors: - drop_fields: fields: ['field'] when: ... ``` The exactly same processor structure will be created in either case. Alternatively these 3 `drop_event` processor configurations are all equivalent: ``` processors: - drop_event.when: ... ``` ``` processors: - drop_event: when: ... ``` and ``` processors: - drop_event: when: ... ``` Namespaces come in handy, if some filter supports multiple backends. e.g.: ``` processors: - lookup: exec: ... when: ... ``` or ``` processors: - lookup: file: ... when: ... ``` Using namespaces, the `when`-clause can be used on any level, making all these configurations equivalent: ``` processors: - lookup: exec: ... when: ... ``` ``` processors: - lookup: exec: ... when: ... ``` and ``` processors: - lookup: exec: ... when: ... ``` This minimizes risk of configuration errors when copying conditions or indentation is of (a little). Plus the aforementioned filter can be written more conveniently: ``` processors: - lookup.exec: ... when: ... ```
- Loading branch information
Showing
7 changed files
with
252 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package processors | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
) | ||
|
||
type Namespace struct { | ||
reg map[string]pluginer | ||
} | ||
|
||
type plugin struct { | ||
c Constructor | ||
} | ||
|
||
type pluginer interface { | ||
Plugin() Constructor | ||
} | ||
|
||
func NewNamespace() *Namespace { | ||
return &Namespace{ | ||
reg: map[string]pluginer{}, | ||
} | ||
} | ||
|
||
func (ns *Namespace) Register(name string, factory Constructor) error { | ||
p := plugin{NewConditional(factory)} | ||
names := strings.Split(name, ".") | ||
if err := ns.add(names, p); err != nil { | ||
return fmt.Errorf("plugin %s registration fail %v", name, err) | ||
} | ||
return nil | ||
} | ||
|
||
func (ns *Namespace) add(names []string, p pluginer) error { | ||
name := names[0] | ||
|
||
// register plugin if intermediate node in path being processed | ||
if len(names) == 1 { | ||
if _, found := ns.reg[name]; found { | ||
return errors.New("exists already") | ||
} | ||
|
||
ns.reg[name] = p | ||
return nil | ||
} | ||
|
||
// check if namespace path already exists | ||
tmp, found := ns.reg[name] | ||
if found { | ||
ns, ok := tmp.(*Namespace) | ||
if !ok { | ||
return errors.New("non-namespace plugin already registered") | ||
} | ||
return ns.add(names[1:], p) | ||
} | ||
|
||
// register new namespace | ||
sub := NewNamespace() | ||
err := sub.add(names[1:], p) | ||
if err != nil { | ||
return err | ||
} | ||
ns.reg[name] = sub | ||
return nil | ||
} | ||
|
||
func (ns *Namespace) Plugin() Constructor { | ||
return NewConditional(func(cfg common.Config) (Processor, error) { | ||
var section string | ||
for _, name := range cfg.GetFields() { | ||
if name == "when" { // TODO: remove check for "when" once fields are filtered | ||
continue | ||
} | ||
|
||
if section != "" { | ||
return nil, fmt.Errorf("Too many lookup modules configured (%v, %v)", | ||
section, name) | ||
} | ||
|
||
section = name | ||
} | ||
|
||
if section == "" { | ||
return nil, errors.New("No lookup module configured") | ||
} | ||
|
||
backend, found := ns.reg[section] | ||
if !found { | ||
return nil, fmt.Errorf("Unknown lookup module: %v", section) | ||
} | ||
|
||
config, err := cfg.Child(section, -1) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
constructor := backend.Plugin() | ||
return constructor(*config) | ||
}) | ||
} | ||
|
||
func (p plugin) Plugin() Constructor { return p.c } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package processors | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/elastic/beats/libbeat/common" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
type testFilterRule struct { | ||
str func() string | ||
run func(common.MapStr) (common.MapStr, error) | ||
} | ||
|
||
func TestNamespace(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
}{ | ||
{"test"}, | ||
{"test.test"}, | ||
{"abc.def.test"}, | ||
} | ||
|
||
for i, test := range tests { | ||
t.Logf("run (%v): %v", i, test.name) | ||
|
||
ns := NewNamespace() | ||
err := ns.Register(test.name, newTestFilterRule) | ||
fatalError(t, err) | ||
|
||
cfg, _ := common.NewConfigFrom(map[string]interface{}{ | ||
test.name: nil, | ||
}) | ||
|
||
filter, err := ns.Plugin()(*cfg) | ||
|
||
assert.NoError(t, err) | ||
assert.NotNil(t, filter) | ||
} | ||
} | ||
|
||
func TestNamespaceRegisterFail(t *testing.T) { | ||
ns := NewNamespace() | ||
err := ns.Register("test", newTestFilterRule) | ||
fatalError(t, err) | ||
|
||
err = ns.Register("test", newTestFilterRule) | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestNamespaceError(t *testing.T) { | ||
tests := []struct { | ||
title string | ||
factory Constructor | ||
config interface{} | ||
}{ | ||
{ | ||
"no module configured", | ||
newTestFilterRule, | ||
map[string]interface{}{}, | ||
}, | ||
{ | ||
"unknown module configured", | ||
newTestFilterRule, | ||
map[string]interface{}{ | ||
"notTest": nil, | ||
}, | ||
}, | ||
{ | ||
"too many modules", | ||
newTestFilterRule, | ||
map[string]interface{}{ | ||
"a": nil, | ||
"b": nil, | ||
"test": nil, | ||
}, | ||
}, | ||
{ | ||
"filter init fail", | ||
func(_ common.Config) (Processor, error) { | ||
return nil, errors.New("test") | ||
}, | ||
map[string]interface{}{ | ||
"test": nil, | ||
}, | ||
}, | ||
} | ||
|
||
for i, test := range tests { | ||
t.Logf("run (%v): %v", i, test.title) | ||
|
||
ns := NewNamespace() | ||
err := ns.Register("test", test.factory) | ||
fatalError(t, err) | ||
|
||
config, err := common.NewConfigFrom(test.config) | ||
fatalError(t, err) | ||
|
||
_, err = ns.Plugin()(*config) | ||
assert.Error(t, err) | ||
} | ||
} | ||
|
||
func newTestFilterRule(_ common.Config) (Processor, error) { | ||
return &testFilterRule{}, nil | ||
} | ||
|
||
func (r *testFilterRule) String() string { | ||
if r.str == nil { | ||
return "test" | ||
} | ||
return r.str() | ||
} | ||
|
||
func (r *testFilterRule) Run(evt common.MapStr) (common.MapStr, error) { | ||
if r.run == nil { | ||
return evt, nil | ||
} | ||
return r.Run(evt) | ||
} | ||
|
||
func fatalError(t *testing.T, err error) { | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters