Normalize event configuration (#1444)
* Normalize event configuration This updates event configuration stanzas to more closely match behavior for listeners and kmses: * Singular "sink" instead of "sinks" (probably not the most correct option, but a consistent option, and one that makes sense for HCL) * Allows sinks to have the type defined at the start of the block * Moves type-specific configuration to a specific named block Co-authored-by: Jim <jlambert@hashicorp.com>
This commit is contained in:
1 parent
00ada3c578
commit
c75c97a872
14 files changed
+412
-117
No files matched your search
@@ -11,7 +11,7 @@ require (
|
||||
github.com/bufbuild/buf v0.37.0
|
||||
github.com/dhui/dktest v0.3.4
|
||||
github.com/fatih/color v1.12.0
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/fatih/structs v1.1.0
|
||||
github.com/favadi/protoc-go-inject-tag v1.1.0
|
||||
github.com/golang-migrate/migrate/v4 v4.14.1
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe
|
||||
|
||||
@@ -37,8 +37,8 @@ func Test_GetOpts(t *testing.T) {
|
||||
// not a valid sink, but it doesn't need to be to test the
|
||||
// option is properly supported.
|
||||
{
|
||||
Name: "test-sink",
|
||||
SinkType: "Stderr",
|
||||
Name: "test-sink",
|
||||
Type: "Stderr",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||
"github.com/hashicorp/go-secure-stdlib/strutil"
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/hashicorp/hcl/hcl/ast"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
@@ -277,7 +278,6 @@ func Parse(d string) (*Config, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Nothing to do here right now
|
||||
result := New()
|
||||
if err := hcl.DecodeObject(result, obj); err != nil {
|
||||
return nil, err
|
||||
@@ -408,17 +408,84 @@ func Parse(d string) (*Config, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if result.Eventing == nil {
|
||||
list, ok := obj.Node.(*ast.ObjectList)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("error parsing: file doesn't contain a root object")
|
||||
}
|
||||
|
||||
eventList := list.Filter("events")
|
||||
switch len(eventList.Items) {
|
||||
case 0:
|
||||
result.Eventing = event.DefaultEventerConfig()
|
||||
} else {
|
||||
if result.Eventing.Sinks == nil {
|
||||
result.Eventing.Sinks = []event.SinkConfig{event.DefaultSink()}
|
||||
case 1:
|
||||
if result.Eventing, err = parseEventing(eventList.Items[0]); err != nil {
|
||||
return nil, fmt.Errorf(`error parsing "events": %w`, err)
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf(`too many "events" nodes (max 1, got %d)`, len(eventList.Items))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func parseEventing(eventObj *ast.ObjectItem) (*event.EventerConfig, error) {
|
||||
// Decode the outside struct
|
||||
var result event.EventerConfig
|
||||
if err := hcl.DecodeObject(&result, eventObj.Val); err != nil {
|
||||
return nil, fmt.Errorf(`error decoding "events" node: %w`, err)
|
||||
}
|
||||
// Now, find the sinks
|
||||
eventObjType, ok := eventObj.Val.(*ast.ObjectType)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf(`error interpreting "events" node as an object type`)
|
||||
}
|
||||
list := eventObjType.List
|
||||
sinkList := list.Filter("sink")
|
||||
// Go through each sink and decode
|
||||
for i, item := range sinkList.Items {
|
||||
var s event.SinkConfig
|
||||
if err := hcl.DecodeObject(&s, item.Val); err != nil {
|
||||
return nil, fmt.Errorf("error decoding eventer sink entry %d", i)
|
||||
}
|
||||
|
||||
// Fix up type and validate
|
||||
switch {
|
||||
case s.Type != "":
|
||||
case len(item.Keys) == 1:
|
||||
s.Type = event.SinkType(item.Keys[0].Token.Value().(string))
|
||||
default:
|
||||
switch {
|
||||
case s.StderrConfig != nil:
|
||||
// If we haven't found the type any other way, they _must_
|
||||
// specify this block even though there are no config parameters
|
||||
s.Type = event.StderrSink
|
||||
case s.FileConfig != nil:
|
||||
s.Type = event.FileSink
|
||||
default:
|
||||
return nil, fmt.Errorf("sink type could not be determined")
|
||||
}
|
||||
}
|
||||
s.Type = event.SinkType(strings.ToLower(string(s.Type)))
|
||||
|
||||
if s.Type == event.StderrSink && s.StderrConfig == nil {
|
||||
// StderrConfig is optional as it has no values, but ensure it's
|
||||
// always populated if it's the type
|
||||
s.StderrConfig = new(event.StderrSinkTypeConfig)
|
||||
}
|
||||
|
||||
if err := s.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Append to result
|
||||
result.Sinks = append(result.Sinks, s)
|
||||
}
|
||||
if len(result.Sinks) == 0 {
|
||||
result.Sinks = []event.SinkConfig{event.DefaultSink()}
|
||||
}
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// Sanitized returns a copy of the config with all values that are considered
|
||||
// sensitive stripped. It also strips all `*Raw` values that are mainly
|
||||
// used for parsing.
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/boundary/internal/errors"
|
||||
"github.com/hashicorp/boundary/internal/observability/event"
|
||||
"github.com/hashicorp/go-secure-stdlib/configutil"
|
||||
"github.com/hashicorp/go-secure-stdlib/listenerutil"
|
||||
@@ -329,9 +328,9 @@ func TestController_EventingConfig(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
config string
|
||||
config []string
|
||||
wantEventerConfig *event.EventerConfig
|
||||
wantErrMatch *errors.Template
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
@@ -339,11 +338,11 @@ func TestController_EventingConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "audit-enabled",
|
||||
config: `
|
||||
config: []string{`
|
||||
events {
|
||||
audit_enabled = true
|
||||
}
|
||||
`,
|
||||
`},
|
||||
wantEventerConfig: &event.EventerConfig{
|
||||
AuditEnabled: true,
|
||||
ObservationsEnabled: false,
|
||||
@@ -354,11 +353,11 @@ func TestController_EventingConfig(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "observations-enabled",
|
||||
config: `
|
||||
config: []string{`
|
||||
events {
|
||||
observations_enabled = true
|
||||
}
|
||||
`,
|
||||
`},
|
||||
wantEventerConfig: &event.EventerConfig{
|
||||
AuditEnabled: false,
|
||||
ObservationsEnabled: true,
|
||||
@@ -368,28 +367,120 @@ func TestController_EventingConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sinks-configured",
|
||||
config: `
|
||||
events {
|
||||
name: "no-sink-type-determined",
|
||||
config: []string{
|
||||
`events {
|
||||
audit_enabled = false
|
||||
observations_enabled = true
|
||||
sinks = [
|
||||
{
|
||||
name = "configured-sink"
|
||||
sink {
|
||||
format = "cloudevents-json"
|
||||
name = "configured-sink"
|
||||
event_types = [ "audit", "observation" ]
|
||||
}
|
||||
}`,
|
||||
},
|
||||
wantErr: `error parsing "events": sink type could not be determined`,
|
||||
},
|
||||
{
|
||||
name: "sinks-configured",
|
||||
config: []string{
|
||||
`events {
|
||||
audit_enabled = false
|
||||
observations_enabled = true
|
||||
sink {
|
||||
type = "file"
|
||||
format = "cloudevents-json"
|
||||
name = "configured-sink"
|
||||
event_types = [ "audit", "observation" ]
|
||||
file {
|
||||
file_name = "file-name"
|
||||
event_types = [ "audit", "observation" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
`,
|
||||
}
|
||||
sink {
|
||||
type = "stderr"
|
||||
format = "hclog-text"
|
||||
name = "stderr-sink"
|
||||
event_types = [ "error" ]
|
||||
}
|
||||
}`,
|
||||
`events {
|
||||
audit_enabled = false
|
||||
observations_enabled = true
|
||||
sink "file" {
|
||||
format = "cloudevents-json"
|
||||
name = "configured-sink"
|
||||
event_types = [ "audit", "observation" ]
|
||||
file {
|
||||
file_name = "file-name"
|
||||
}
|
||||
}
|
||||
sink "stderr" {
|
||||
format = "hclog-text"
|
||||
name = "stderr-sink"
|
||||
event_types = [ "error" ]
|
||||
}
|
||||
}`,
|
||||
`events {
|
||||
audit_enabled = false
|
||||
observations_enabled = true
|
||||
sink {
|
||||
format = "cloudevents-json"
|
||||
name = "configured-sink"
|
||||
event_types = [ "audit", "observation" ]
|
||||
file {
|
||||
file_name = "file-name"
|
||||
}
|
||||
}
|
||||
sink {
|
||||
format = "hclog-text"
|
||||
name = "stderr-sink"
|
||||
event_types = [ "error" ]
|
||||
stderr = {}
|
||||
}
|
||||
}`,
|
||||
`{
|
||||
"events": {
|
||||
"audit_enabled": false,
|
||||
"observations_enabled": true,
|
||||
"sink": [
|
||||
{
|
||||
"type": "file",
|
||||
"format": "cloudevents-json",
|
||||
"name": "configured-sink",
|
||||
"event_types": ["audit", "observation"],
|
||||
"file": {
|
||||
"file_name": "file-name"
|
||||
}
|
||||
},
|
||||
{
|
||||
"format": "hclog-text",
|
||||
"name": "stderr-sink",
|
||||
"event_types": ["error"],
|
||||
"stderr": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
}`,
|
||||
},
|
||||
wantEventerConfig: &event.EventerConfig{
|
||||
AuditEnabled: false,
|
||||
ObservationsEnabled: true,
|
||||
Sinks: []event.SinkConfig{
|
||||
{
|
||||
Type: "file",
|
||||
Name: "configured-sink",
|
||||
FileName: "file-name",
|
||||
Format: "cloudevents-json",
|
||||
EventTypes: []event.Type{"audit", "observation"},
|
||||
FileConfig: &event.FileSinkTypeConfig{
|
||||
FileName: "file-name",
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: "stderr",
|
||||
Name: "stderr-sink",
|
||||
Format: "hclog-text",
|
||||
EventTypes: []event.Type{"error"},
|
||||
StderrConfig: &event.StderrSinkTypeConfig{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -398,16 +489,18 @@ func TestController_EventingConfig(t *testing.T) {
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
c, err := Parse(tt.config)
|
||||
if tt.wantErrMatch != nil {
|
||||
for i, conf := range tt.config {
|
||||
c, err := Parse(conf)
|
||||
if tt.wantErr != "" {
|
||||
require.Error(err)
|
||||
assert.Empty(c)
|
||||
assert.Equal(tt.wantErr, err.Error(), "config %d want %q and got %q", i, tt.wantErr, err.Error())
|
||||
return
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.Empty(c)
|
||||
assert.Truef(errors.Match(tt.wantErrMatch, err), "want %q and got %q", tt.wantErrMatch.Code, err.Error())
|
||||
return
|
||||
assert.NotEmpty(c)
|
||||
assert.Equal(tt.wantEventerConfig, c.Eventing)
|
||||
}
|
||||
require.NoError(err)
|
||||
assert.NotEmpty(c)
|
||||
assert.Equal(tt.wantEventerConfig, c.Eventing)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ const (
|
||||
SystemType Type = "system" // SysType represents system events
|
||||
)
|
||||
|
||||
func (et Type) validate() error {
|
||||
const op = "event.(Type).validate"
|
||||
func (et Type) Validate() error {
|
||||
const op = "event.(Type).Validate"
|
||||
switch et {
|
||||
case EveryType, ObservationType, AuditType, ErrorType, SystemType:
|
||||
return nil
|
||||
|
||||
@@ -203,7 +203,7 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
|
||||
|
||||
var sinkId eventlogger.NodeID
|
||||
var sinkNode eventlogger.Node
|
||||
switch s.SinkType {
|
||||
switch s.Type {
|
||||
case StderrSink:
|
||||
sinkNode = &writer.Sink{
|
||||
Format: string(s.Format),
|
||||
@@ -214,23 +214,27 @@ func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName stri
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
sinkId = eventlogger.NodeID(id)
|
||||
default:
|
||||
if _, found := allSinkFilenames[s.Path+s.FileName]; found {
|
||||
return nil, fmt.Errorf("%s: duplicate file sink: %s %s", op, s.Path, s.FileName)
|
||||
case FileSink:
|
||||
fsc := s.FileConfig
|
||||
if _, found := allSinkFilenames[fsc.Path+fsc.FileName]; found {
|
||||
return nil, fmt.Errorf("%s: duplicate file sink: %s %s: %w", op, fsc.Path, fsc.FileName, ErrInvalidParameter)
|
||||
}
|
||||
allSinkFilenames[fsc.Path+fsc.FileName] = true
|
||||
sinkNode = &eventlogger.FileSink{
|
||||
Format: string(s.Format),
|
||||
Path: s.Path,
|
||||
FileName: s.FileName,
|
||||
MaxBytes: s.RotateBytes,
|
||||
MaxDuration: s.RotateDuration,
|
||||
MaxFiles: s.RotateMaxFiles,
|
||||
Path: fsc.Path,
|
||||
FileName: fsc.FileName,
|
||||
MaxBytes: fsc.RotateBytes,
|
||||
MaxDuration: fsc.RotateDuration,
|
||||
MaxFiles: fsc.RotateMaxFiles,
|
||||
}
|
||||
id, err := NewId(fmt.Sprintf("file_%s_%s_", s.Path, s.FileName))
|
||||
id, err := NewId(fmt.Sprintf("file_%s_%s_", fsc.Path, fsc.FileName))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
sinkId = eventlogger.NodeID(id)
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: unknown sink type %s", op, s.Type)
|
||||
}
|
||||
err = e.broker.RegisterNode(sinkId, sinkNode)
|
||||
if err != nil {
|
||||
@@ -474,7 +478,7 @@ func DefaultSink() SinkConfig {
|
||||
Name: "default",
|
||||
EventTypes: []Type{EveryType},
|
||||
Format: JSONSinkFormat,
|
||||
SinkType: StderrSink,
|
||||
Type: StderrSink,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -610,7 +614,7 @@ func (e *Eventer) StandardWriter(ctx context.Context, typ Type) (io.Writer, erro
|
||||
if typ == "" {
|
||||
return nil, fmt.Errorf("%s: missing type: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
if err := typ.validate(); err != nil {
|
||||
if err := typ.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
newEventer := *e
|
||||
|
||||
@@ -9,7 +9,7 @@ type EventerConfig struct {
|
||||
AuditEnabled bool `hcl:"audit_enabled"` // AuditEnabled specifies if audit events should be emitted.
|
||||
ObservationsEnabled bool `hcl:"observations_enabled"` // ObservationsEnabled specifies if observation events should be emitted.
|
||||
SysEventsEnabled bool `hcl:"sysevents_enabled"` // SysEventsEnabled specifies if sysevents should be emitted.
|
||||
Sinks []SinkConfig `hcl:"sinks"` // Sinks are all the configured sinks
|
||||
Sinks []SinkConfig `hcl:"-"` // Sinks are all the configured sinks
|
||||
}
|
||||
|
||||
// Validate will Validate the config. A config isn't required to have any
|
||||
@@ -17,7 +17,7 @@ type EventerConfig struct {
|
||||
func (c *EventerConfig) Validate() error {
|
||||
const op = "event.(EventerConfig).Validate"
|
||||
for i, s := range c.Sinks {
|
||||
if err := s.validate(); err != nil {
|
||||
if err := s.Validate(); err != nil {
|
||||
return fmt.Errorf("%s: sink %d is invalid: %w", op, i, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEventerConfig_validate(t *testing.T) {
|
||||
func TestEventerConfig_Validate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
c EventerConfig
|
||||
@@ -19,7 +19,7 @@ func TestEventerConfig_validate(t *testing.T) {
|
||||
c: EventerConfig{
|
||||
Sinks: []SinkConfig{
|
||||
{
|
||||
SinkType: "invalid",
|
||||
Type: "invalid",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -113,7 +113,7 @@ func Test_InitSysEventer(t *testing.T) {
|
||||
Name: "default",
|
||||
EventTypes: []Type{EveryType},
|
||||
Format: JSONSinkFormat,
|
||||
SinkType: StderrSink,
|
||||
Type: StderrSink,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -396,6 +396,29 @@ func Test_NewEventer(t *testing.T) {
|
||||
lock: testLock,
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "dup-sink-filename",
|
||||
config: func() EventerConfig {
|
||||
dupFileConfig := TestEventerConfig(t, "dup-sink-filename")
|
||||
dupFileConfig.EventerConfig.Sinks = append(dupFileConfig.EventerConfig.Sinks,
|
||||
SinkConfig{
|
||||
Name: "err-file-sink",
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{ErrorType},
|
||||
Format: JSONSinkFormat,
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
Path: "./",
|
||||
FileName: dupFileConfig.ErrorEvents.Name(),
|
||||
},
|
||||
},
|
||||
)
|
||||
return dupFileConfig.EventerConfig
|
||||
}(),
|
||||
logger: testLogger,
|
||||
lock: testLock,
|
||||
serverName: "dup-sink-filename",
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
},
|
||||
{
|
||||
name: "success-with-default-config",
|
||||
config: EventerConfig{},
|
||||
@@ -410,7 +433,7 @@ func Test_NewEventer(t *testing.T) {
|
||||
Name: "default",
|
||||
EventTypes: []Type{EveryType},
|
||||
Format: JSONSinkFormat,
|
||||
SinkType: StderrSink,
|
||||
Type: StderrSink,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -7,31 +7,54 @@ import (
|
||||
|
||||
// SinkConfig defines the configuration for a Eventer sink
|
||||
type SinkConfig struct {
|
||||
Name string `hcl:"name"` // Name defines a name for the sink.
|
||||
Description string `hcl:"description"` // Description defines a description for the sink.
|
||||
EventTypes []Type `hcl:"event_types"` // EventTypes defines a list of event types that will be sent to the sink. See the docs for EventTypes for a list of accepted values.
|
||||
EventSourceUrl string `hcl:"event_source_url"` // EventSource defines an optional event source URL for the sink. If not defined a default source will be composed of the https://hashicorp.com/boundary.io/ServerName/Path/FileName
|
||||
AllowFilters []string `hcl:"allow_filters"` // AllowFilters define a set predicates for including an event in the sink. If any filter matches, the event will be included. The filter should be in a format supported by hashicorp/go-bexpr.
|
||||
DenyFilters []string `hcl:"deny_filters"` // DenyFilters define a set predicates for excluding an event in the sink. If any filter matches, the event will be excluded. The filter should be in a format supported by hashicorp/go-bexpr.
|
||||
SinkType SinkType `hcl:"sink_type"` // SinkType defines the type of sink (StderrSink or FileSink)
|
||||
Format SinkFormat `hcl:"format"` // Format defines the format for the sink (JSONSinkFormat or TextSinkFormat)
|
||||
Path string `hcl:"path"` // Path defines the file path for the sink
|
||||
FileName string `hcl:"file_name"` // FileName defines the file name for the sink
|
||||
RotateBytes int `hcl:"rotate_bytes"` // RotateByes defines the number of bytes that should trigger rotation of a FileSink
|
||||
RotateDuration time.Duration `hcl:"rotate_duration"` // RotateDuration defines how often a FileSink should be rotated
|
||||
RotateMaxFiles int `hcl:"rotate_max_files"` // RotateMaxFiles defines how may historical rotated files should be kept for a FileSink
|
||||
Name string `hcl:"name"` // Name defines a name for the sink.
|
||||
Description string `hcl:"description"` // Description defines a description for the sink.
|
||||
EventTypes []Type `hcl:"event_types"` // EventTypes defines a list of event types that will be sent to the sink. See the docs for EventTypes for a list of accepted values.
|
||||
EventSourceUrl string `hcl:"event_source_url"` // EventSource defines an optional event source URL for the sink. If not defined a default source will be composed of the https://hashicorp.com/boundary.io/ServerName/Path/FileName.
|
||||
AllowFilters []string `hcl:"allow_filters"` // AllowFilters define a set predicates for including an event in the sink. If any filter matches, the event will be included. The filter should be in a format supported by hashicorp/go-bexpr.
|
||||
DenyFilters []string `hcl:"deny_filters"` // DenyFilters define a set predicates for excluding an event in the sink. If any filter matches, the event will be excluded. The filter should be in a format supported by hashicorp/go-bexpr.
|
||||
Format SinkFormat `hcl:"format"` // Format defines the format for the sink (JSONSinkFormat or TextSinkFormat).
|
||||
Type SinkType `hcl:"type"` // Type defines the type of sink (StderrSink or FileSink).
|
||||
StderrConfig *StderrSinkTypeConfig `hcl:"stderr"` // StderrConfig defines parameters for a stderr output.
|
||||
FileConfig *FileSinkTypeConfig `hcl:"file"` // FileConfig defines parameters for a file output.
|
||||
}
|
||||
|
||||
func (sc *SinkConfig) validate() error {
|
||||
const op = "event.(SinkConfig).validate"
|
||||
if err := sc.SinkType.validate(); err != nil {
|
||||
func (sc *SinkConfig) Validate() error {
|
||||
const op = "event.(SinkConfig).Validate"
|
||||
if err := sc.Type.Validate(); err != nil {
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
if err := sc.Format.Validate(); err != nil {
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
if sc.SinkType == FileSink && sc.FileName == "" {
|
||||
return fmt.Errorf("%s: missing sink file name: %w", op, ErrInvalidParameter)
|
||||
|
||||
var foundSinkTypeConfigs int
|
||||
if sc.StderrConfig != nil {
|
||||
foundSinkTypeConfigs++
|
||||
}
|
||||
if sc.FileConfig != nil {
|
||||
foundSinkTypeConfigs++
|
||||
}
|
||||
if foundSinkTypeConfigs > 1 {
|
||||
return fmt.Errorf("%s: too many sink type config blocks: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
|
||||
switch sc.Type {
|
||||
case StderrSink:
|
||||
// It's okay for StderrConfig to be nil if the type is stderr, but it's
|
||||
// not okay for something else to be set
|
||||
if foundSinkTypeConfigs == 1 && sc.StderrConfig == nil {
|
||||
return fmt.Errorf("%s: mismatch between sink type and sink configuration block: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
case FileSink:
|
||||
// Unlike in the stderr case, this can't be nil, so if it's not nil
|
||||
// we've now verified it's the only block populated
|
||||
if sc.FileConfig == nil {
|
||||
return fmt.Errorf(`%s: missing "file" block: %w`, op, ErrInvalidParameter)
|
||||
}
|
||||
if sc.FileConfig.FileName == "" {
|
||||
return fmt.Errorf("%s: missing file name: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
}
|
||||
if sc.Name == "" {
|
||||
return fmt.Errorf("%s: missing sink name: %w", op, ErrInvalidParameter)
|
||||
@@ -40,13 +63,25 @@ func (sc *SinkConfig) validate() error {
|
||||
return fmt.Errorf("%s: missing event types: %w", op, ErrInvalidParameter)
|
||||
}
|
||||
for _, et := range sc.EventTypes {
|
||||
if err := et.validate(); err != nil {
|
||||
if err := et.Validate(); err != nil {
|
||||
return fmt.Errorf("%s: %w", op, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StderrSinkTypeConfig contains configuration structures for file sink types
|
||||
type StderrSinkTypeConfig struct{}
|
||||
|
||||
// FileSinkTypeConfig contains configuration structures for file sink types
|
||||
type FileSinkTypeConfig struct {
|
||||
Path string `hcl:"path" mapstructure:"path"` // Path defines the file path for the sink
|
||||
FileName string `hcl:"file_name" mapstructure:"file_name"` // FileName defines the file name for the sink
|
||||
RotateBytes int `hcl:"rotate_bytes" mapstructure:"rotate_bytes"` // RotateByes defines the number of bytes that should trigger rotation of a FileSink
|
||||
RotateDuration time.Duration `hcl:"rotate_duration" mapstructure:"rotate_duration"` // RotateDuration defines how often a FileSink should be rotated
|
||||
RotateMaxFiles int `hcl:"rotate_max_files" mapstructure:"rotate_max_files"` // RotateMaxFiles defines how may historical rotated files should be kept for a FileSink
|
||||
}
|
||||
|
||||
// FilterType defines a type for filters (allow or deny)
|
||||
type FilterType string
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSinkConfig_validate(t *testing.T) {
|
||||
func TestSinkConfig_Validate(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -19,9 +19,11 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
name: "missing-name",
|
||||
sc: SinkConfig{
|
||||
EventTypes: []Type{EveryType},
|
||||
SinkType: FileSink,
|
||||
FileName: "tmp.file",
|
||||
Format: JSONSinkFormat,
|
||||
Type: FileSink,
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
Format: JSONSinkFormat,
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "missing sink name",
|
||||
@@ -29,10 +31,12 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
{
|
||||
name: "missing-EventType",
|
||||
sc: SinkConfig{
|
||||
Name: "sink-name",
|
||||
SinkType: FileSink,
|
||||
FileName: "tmp.file",
|
||||
Format: JSONSinkFormat,
|
||||
Name: "sink-name",
|
||||
Type: FileSink,
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
Format: JSONSinkFormat,
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "missing event types",
|
||||
@@ -42,9 +46,11 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
sc: SinkConfig{
|
||||
Name: "sink-name",
|
||||
EventTypes: []Type{"invalid"},
|
||||
SinkType: FileSink,
|
||||
FileName: "tmp.file",
|
||||
Format: JSONSinkFormat,
|
||||
Type: FileSink,
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
Format: JSONSinkFormat,
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "not a valid event type",
|
||||
@@ -54,8 +60,10 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
sc: SinkConfig{
|
||||
Name: "sink-name",
|
||||
EventTypes: []Type{EveryType},
|
||||
FileName: "tmp.file",
|
||||
Format: JSONSinkFormat,
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
Format: JSONSinkFormat,
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "not a valid sink type",
|
||||
@@ -65,9 +73,11 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
sc: SinkConfig{
|
||||
Name: "sink-name",
|
||||
EventTypes: []Type{EveryType},
|
||||
SinkType: "invalid",
|
||||
FileName: "tmp.file",
|
||||
Format: JSONSinkFormat,
|
||||
Type: "invalid",
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
Format: JSONSinkFormat,
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "not a valid sink type",
|
||||
@@ -76,9 +86,11 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
name: "missing-format",
|
||||
sc: SinkConfig{
|
||||
Name: "sink-name",
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{EveryType},
|
||||
FileName: "tmp.file",
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "not a valid sink format",
|
||||
@@ -88,9 +100,11 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
sc: SinkConfig{
|
||||
Name: "sink-name",
|
||||
Format: "invalid",
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{EveryType},
|
||||
FileName: "tmp.file",
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "not a valid sink format",
|
||||
@@ -99,27 +113,76 @@ func TestSinkConfig_validate(t *testing.T) {
|
||||
name: "file-sink-with-no-file-name",
|
||||
sc: SinkConfig{
|
||||
EventTypes: []Type{EveryType},
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
Format: JSONSinkFormat,
|
||||
FileConfig: &FileSinkTypeConfig{},
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: "missing sink file name",
|
||||
wantErrContains: "missing file name",
|
||||
},
|
||||
{
|
||||
name: "type mismatch file type stderr config",
|
||||
sc: SinkConfig{
|
||||
EventTypes: []Type{EveryType},
|
||||
Type: FileSink,
|
||||
Format: JSONSinkFormat,
|
||||
StderrConfig: &StderrSinkTypeConfig{},
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: `missing "file" block`,
|
||||
},
|
||||
{
|
||||
name: "type mismatch stderr type file config",
|
||||
sc: SinkConfig{
|
||||
EventTypes: []Type{EveryType},
|
||||
Type: StderrSink,
|
||||
Format: JSONSinkFormat,
|
||||
FileConfig: &FileSinkTypeConfig{},
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: `mismatch between sink type and sink configuration block`,
|
||||
},
|
||||
{
|
||||
name: "type mismatch both types file config",
|
||||
sc: SinkConfig{
|
||||
EventTypes: []Type{EveryType},
|
||||
Type: FileSink,
|
||||
Format: JSONSinkFormat,
|
||||
StderrConfig: &StderrSinkTypeConfig{},
|
||||
FileConfig: &FileSinkTypeConfig{},
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: `too many sink type config blocks`,
|
||||
},
|
||||
{
|
||||
name: "type mismatch both types stderr config",
|
||||
sc: SinkConfig{
|
||||
EventTypes: []Type{EveryType},
|
||||
Type: StderrSink,
|
||||
Format: JSONSinkFormat,
|
||||
StderrConfig: &StderrSinkTypeConfig{},
|
||||
FileConfig: &FileSinkTypeConfig{},
|
||||
},
|
||||
wantErrIs: ErrInvalidParameter,
|
||||
wantErrContains: `too many sink type config blocks`,
|
||||
},
|
||||
{
|
||||
name: "valid",
|
||||
sc: SinkConfig{
|
||||
Name: "valid",
|
||||
EventTypes: []Type{EveryType},
|
||||
SinkType: FileSink,
|
||||
FileName: "tmp.file",
|
||||
Format: JSONSinkFormat,
|
||||
Type: FileSink,
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
FileName: "tmp.file",
|
||||
},
|
||||
Format: JSONSinkFormat,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert, require := assert.New(t), require.New(t)
|
||||
err := tt.sc.validate()
|
||||
err := tt.sc.Validate()
|
||||
if tt.wantErrIs != nil {
|
||||
require.Error(err)
|
||||
assert.ErrorIs(err, tt.wantErrIs)
|
||||
|
||||
@@ -11,7 +11,7 @@ const (
|
||||
|
||||
type SinkType string // SinkType defines the type of sink in a config stanza (file, stderr)
|
||||
|
||||
func (t SinkType) validate() error {
|
||||
func (t SinkType) Validate() error {
|
||||
const op = "event.(SinkType).validate"
|
||||
switch t {
|
||||
case StderrSink, FileSink:
|
||||
|
||||
@@ -79,25 +79,29 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
|
||||
Sinks: []SinkConfig{
|
||||
{
|
||||
Name: "every-type-file-sink",
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{EveryType},
|
||||
Format: opts.withSinkFormat,
|
||||
Path: "./",
|
||||
FileName: tmpAllFile.Name(),
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
Path: "./",
|
||||
FileName: tmpAllFile.Name(),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "stderr",
|
||||
SinkType: StderrSink,
|
||||
Type: StderrSink,
|
||||
EventTypes: []Type{EveryType},
|
||||
Format: opts.withSinkFormat,
|
||||
},
|
||||
{
|
||||
Name: "err-file-sink",
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{ErrorType},
|
||||
Format: opts.withSinkFormat,
|
||||
Path: "./",
|
||||
FileName: tmpErrFile.Name(),
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
Path: "./",
|
||||
FileName: tmpErrFile.Name(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -112,11 +116,13 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
|
||||
})
|
||||
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, SinkConfig{
|
||||
Name: "audit-file-sink",
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{AuditType},
|
||||
Format: opts.withSinkFormat,
|
||||
Path: "./",
|
||||
FileName: tmpFile.Name(),
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
Path: "./",
|
||||
FileName: tmpFile.Name(),
|
||||
},
|
||||
})
|
||||
c.AuditEvents = tmpFile
|
||||
}
|
||||
@@ -128,11 +134,13 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
|
||||
})
|
||||
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, SinkConfig{
|
||||
Name: "err-observation-sink",
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{ObservationType},
|
||||
Format: opts.withSinkFormat,
|
||||
Path: "./",
|
||||
FileName: tmpFile.Name(),
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
Path: "./",
|
||||
FileName: tmpFile.Name(),
|
||||
},
|
||||
})
|
||||
c.ObservationEvents = tmpFile
|
||||
}
|
||||
@@ -144,11 +152,13 @@ func TestEventerConfig(t *testing.T, testName string, opt ...Option) TestConfig
|
||||
})
|
||||
c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, SinkConfig{
|
||||
Name: "err-sysevents-sink",
|
||||
SinkType: FileSink,
|
||||
Type: FileSink,
|
||||
EventTypes: []Type{SystemType},
|
||||
Format: opts.withSinkFormat,
|
||||
Path: "./",
|
||||
FileName: tmpFile.Name(),
|
||||
FileConfig: &FileSinkTypeConfig{
|
||||
Path: "./",
|
||||
FileName: tmpFile.Name(),
|
||||
},
|
||||
})
|
||||
}
|
||||
return c
|
||||
|
||||
@@ -240,7 +240,7 @@ func NewTestWorker(t *testing.T, opts *TestWorkerOpts) *TestWorker {
|
||||
}
|
||||
tw.name = opts.Config.Worker.Name
|
||||
|
||||
if err := tw.b.SetupEventing(tw.b.Logger, tw.b.StderrLock, opts.Config.Worker.Name+"/test-worker", base.WithEventerConfig(opts.Config.Eventing)); err != nil {
|
||||
if err := tw.b.SetupEventing(tw.b.Logger, tw.b.StderrLock, opts.Config.Worker.Name, base.WithEventerConfig(opts.Config.Eventing)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user