127 lines
3.7 KiB
Go
127 lines
3.7 KiB
Go
package event
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
filterpkg "github.com/hashicorp/boundary/internal/filter"
|
|
"github.com/hashicorp/eventlogger"
|
|
"github.com/hashicorp/eventlogger/formatter_filters/cloudevents"
|
|
"github.com/hashicorp/go-bexpr"
|
|
)
|
|
|
|
// cloudEventsFormatterFilter represents an eventlogger.cloudEventsFormatterFilter which filters events based on allow and
|
|
// deny bexpr filters
|
|
type cloudEventsFormatterFilter struct {
|
|
*cloudevents.FormatterFilter
|
|
allow []*filter
|
|
deny []*filter
|
|
}
|
|
|
|
// newCloudEventsFormatterFilter creates a new filter node using the optional allow and deny filters
|
|
// provided. Support for WithAllow and WithDeny options.
|
|
func newCloudEventsFormatterFilter(source *url.URL, format cloudevents.Format, opt ...Option) (*cloudEventsFormatterFilter, error) {
|
|
const op = "event.NewCloudEventsNode"
|
|
if source == nil {
|
|
return nil, fmt.Errorf("%s: missing source: %w", op, ErrInvalidParameter)
|
|
}
|
|
switch format {
|
|
case cloudevents.FormatJSON, cloudevents.FormatText:
|
|
default:
|
|
return nil, fmt.Errorf("%s: invalid format '%s': %w", op, format, ErrInvalidParameter)
|
|
}
|
|
opts := getOpts(opt...)
|
|
n := cloudEventsFormatterFilter{
|
|
FormatterFilter: &cloudevents.FormatterFilter{
|
|
Source: source,
|
|
Schema: opts.withSchema,
|
|
Format: format,
|
|
},
|
|
}
|
|
|
|
// intentionally not checking if allow and/or deny optional filters were
|
|
// supplied since having a filter node with no filters is okay.
|
|
|
|
if len(opts.withAllow) > 0 {
|
|
n.allow = make([]*filter, 0, len((opts.withAllow)))
|
|
for i := range opts.withAllow {
|
|
f, err := newFilter(opts.withAllow[i])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: invalid allow filter '%s': %w", op, opts.withAllow[i], err)
|
|
}
|
|
n.allow = append(n.allow, f)
|
|
}
|
|
}
|
|
if len(opts.withDeny) > 0 {
|
|
n.deny = make([]*filter, 0, len((opts.withDeny)))
|
|
for i := range opts.withDeny {
|
|
f, err := newFilter(opts.withDeny[i])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: invalid deny filter '%s': %w", op, opts.withDeny[i], err)
|
|
}
|
|
n.deny = append(n.deny, f)
|
|
}
|
|
}
|
|
n.Predicate = newPredicate(n.allow, n.deny)
|
|
return &n, nil
|
|
}
|
|
|
|
func newPredicate(allow, deny []*filter) func(ctx context.Context, ce interface{}) (bool, error) {
|
|
return func(ctx context.Context, ce interface{}) (bool, error) {
|
|
if len(allow) == 0 && len(deny) == 0 {
|
|
return true, nil
|
|
}
|
|
for _, f := range deny {
|
|
if f.Match(ce) {
|
|
return false, nil
|
|
}
|
|
}
|
|
switch {
|
|
case len(allow) > 0:
|
|
for _, f := range allow {
|
|
if f.Match(ce) {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
default:
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
var _ eventlogger.Node = &cloudEventsFormatterFilter{}
|
|
|
|
type filter struct {
|
|
raw string
|
|
eval *bexpr.Evaluator
|
|
}
|
|
|
|
// newFilter returns a Filter which can be matched against.
|
|
func newFilter(f string) (*filter, error) {
|
|
const op = "event.newFilter"
|
|
if f == "" {
|
|
return nil, fmt.Errorf("%s: missing filter: %w", op, ErrInvalidParameter)
|
|
}
|
|
e, err := bexpr.CreateEvaluator(f, bexpr.WithHookFn(filterpkg.WellKnownTypeFilterHook))
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
return &filter{eval: e, raw: f}, nil
|
|
}
|
|
|
|
// Match returns if the provided interface matches the filter. If the filter
|
|
// does not match the structure of the object being Matched, false is returned.
|
|
func (f *filter) Match(item interface{}) bool {
|
|
if f.eval == nil {
|
|
return true
|
|
}
|
|
m, err := f.eval.Evaluate(item)
|
|
// There isn't a clear way to differentiate between a JSON Pointer which doesn't represent
|
|
// the structure of the object being Matched and a JSON Pointer which references a field which
|
|
// is part of a sub structure that is nil in this item. Because of this, any filter which would
|
|
// result in an error using the underlying library is simply interpreted as not a match.
|
|
return err == nil && m
|
|
}
|