diff --git a/internal/cmd/config/config.go b/internal/cmd/config/config.go index c4a90b003f..01c589510d 100644 --- a/internal/cmd/config/config.go +++ b/internal/cmd/config/config.go @@ -309,14 +309,20 @@ type Plugins struct { } // DevWorker is a Config that is used for dev mode of Boundary -// workers -func DevWorker() (*Config, error) { +// workers. Supported options: WithObservationsEnabled, WithSysEventsEnabled, +// WithAuditEventsEnabled, TestWithErrorEventsEnabled +func DevWorker(opt ...Option) (*Config, error) { workerAuthStorageKey := DevKeyGeneration() hclStr := fmt.Sprintf(devConfig+devWorkerExtraConfig, workerAuthStorageKey) parsed, err := Parse(hclStr) if err != nil { return nil, fmt.Errorf("error parsing dev config: %w", err) } + opts := getOpts(opt...) + parsed.Eventing.AuditEnabled = opts.withAuditEventsEnabled + parsed.Eventing.ObservationsEnabled = opts.withObservationsEnabled + parsed.Eventing.SysEventsEnabled = opts.withSysEventsEnabled + parsed.Eventing.ErrorEventsDisabled = !opts.testWithErrorEventsEnabled return parsed, nil } @@ -339,7 +345,7 @@ func DevKeyGeneration() string { // DevController is a Config that is used for dev mode of Boundary // controllers -func DevController() (*Config, error) { +func DevController(opt ...Option) (*Config, error) { controllerKey := DevKeyGeneration() workerAuthKey := DevKeyGeneration() recoveryKey := DevKeyGeneration() @@ -353,6 +359,11 @@ func DevController() (*Config, error) { parsed.DevControllerKey = controllerKey parsed.DevWorkerAuthKey = workerAuthKey parsed.DevRecoveryKey = recoveryKey + opts := getOpts(opt...) + parsed.Eventing.AuditEnabled = opts.withAuditEventsEnabled + parsed.Eventing.ObservationsEnabled = opts.withObservationsEnabled + parsed.Eventing.SysEventsEnabled = opts.withSysEventsEnabled + parsed.Eventing.ErrorEventsDisabled = !opts.testWithErrorEventsEnabled return parsed, nil } diff --git a/internal/cmd/config/config_test.go b/internal/cmd/config/config_test.go index 04603a25eb..8e5b61afc4 100644 --- a/internal/cmd/config/config_test.go +++ b/internal/cmd/config/config_test.go @@ -105,6 +105,9 @@ func TestDevController(t *testing.T) { }, DevController: true, } + exp.Eventing.ErrorEventsDisabled = true + exp.Eventing.SysEventsEnabled = false + exp.Eventing.ObservationsEnabled = false exp.Listeners[0].RawConfig = actual.Listeners[0].RawConfig exp.Listeners[1].RawConfig = actual.Listeners[1].RawConfig @@ -191,11 +194,10 @@ func TestDevController(t *testing.T) { } func TestDevWorker(t *testing.T) { - actual, err := DevWorker() + actual, err := DevWorker(WithSysEventsEnabled(true), WithObservationsEnabled(true), TestWithErrorEventsEnabled(t, true)) if err != nil { t.Fatal(err) } - exp := &Config{ Eventing: event.DefaultEventerConfig(), SharedConfig: &configutil.SharedConfig{ diff --git a/internal/cmd/config/options.go b/internal/cmd/config/options.go new file mode 100644 index 0000000000..ad7ce0187a --- /dev/null +++ b/internal/cmd/config/options.go @@ -0,0 +1,58 @@ +// Copyright (c) HashiCorp, Inc. + +package config + +import "testing" + +// getOpts - iterate the inbound Options and return a struct +func getOpts(opt ...Option) options { + opts := getDefaultOptions() + for _, o := range opt { + o(&opts) + } + return opts +} + +// Option - how Options are passed as arguments +type Option func(*options) + +// options = how options are represented +type options struct { + withSysEventsEnabled bool + withAuditEventsEnabled bool + withObservationsEnabled bool + testWithErrorEventsEnabled bool +} + +func getDefaultOptions() options { + return options{} +} + +// WithSysEventsEnabled provides an option for enabling system events +func WithSysEventsEnabled(enable bool) Option { + return func(o *options) { + o.withSysEventsEnabled = enable + } +} + +// WithAuditEventsEnabled provides an option for enabling audit events +func WithAuditEventsEnabled(enable bool) Option { + return func(o *options) { + o.withAuditEventsEnabled = enable + } +} + +// WithObservationsEnabled provides an option for enabling observation events +func WithObservationsEnabled(enable bool) Option { + return func(o *options) { + o.withObservationsEnabled = enable + } +} + +// TestWithErrorEventsEnabled provides an option for enabling error events +// during tests. +func TestWithErrorEventsEnabled(_ testing.TB, enable bool) Option { + return func(o *options) { + o.testWithErrorEventsEnabled = enable + } +} diff --git a/internal/daemon/controller/cors_test.go b/internal/daemon/controller/cors_test.go index 52e6922e38..fc7142ec8d 100644 --- a/internal/daemon/controller/cors_test.go +++ b/internal/daemon/controller/cors_test.go @@ -79,6 +79,8 @@ func TestHandler_CORS(t *testing.T) { if err != nil { t.Fatal(err) } + cfg.Eventing.ErrorEventsDisabled = true + cfg.Eventing.SysEventsEnabled = false var wildcardListenerNum int for listenerNum, listener := range cfg.Listeners { diff --git a/internal/daemon/controller/testing.go b/internal/daemon/controller/testing.go index e436a2f1b5..dcf3a2e462 100644 --- a/internal/daemon/controller/testing.go +++ b/internal/daemon/controller/testing.go @@ -7,7 +7,6 @@ import ( "context" "encoding/json" "fmt" - "io" "net" "os" "strconv" @@ -409,10 +408,11 @@ type TestControllerOpts struct { // If true, the controller will not be started DisableAutoStart bool - // DisableEventing, if true the test controller will not create events - // You must not run the test in parallel (no calls to t.Parallel) since the - // this option relies on modifying the system wide default eventer. - DisableEventing bool + // EnableEventing, if true the test controller will create sys and error + // events. You must not run the test in parallel (no calls to t.Parallel) + // since the this option relies on modifying the system wide default + // eventer. + EnableEventing bool // DisableAuthorizationFailures will still cause authz checks to be // performed but they won't cause 403 Forbidden. Useful for API-level @@ -613,39 +613,22 @@ func TestControllerConfig(t testing.TB, ctx context.Context, tc *TestController, } opts.Config.Controller.Scheduler.JobRunIntervalDuration = opts.SchedulerRunJobInterval - switch { - case opts.DisableEventing: + if opts.EnableEventing { opts.Config.Eventing = &event.EventerConfig{ - AuditEnabled: false, - ObservationsEnabled: false, - SysEventsEnabled: false, - } - testLogger := hclog.New(&hclog.LoggerOptions{ - Mutex: tc.b.StderrLock, - Output: io.Discard, - }) - e, err := event.NewEventer( - testLogger, - tc.b.StderrLock, - opts.Config.Controller.Name, - *opts.Config.Eventing, - ) - if err != nil { - t.Fatal(err) - } - tc.b.Eventer = e - event.TestWithoutEventing(t) // this ensures the sys eventer will also stop eventing - default: - - serverName, err := os.Hostname() - if err != nil { - t.Fatal(err) - } - serverName = fmt.Sprintf("%s/controller", serverName) - if err := tc.b.SetupEventing(tc.b.Logger, tc.b.StderrLock, serverName, base.WithEventerConfig(opts.Config.Eventing)); err != nil { - t.Fatal(err) + AuditEnabled: true, + ObservationsEnabled: true, + SysEventsEnabled: true, + ErrorEventsDisabled: true, } } + serverName, err := os.Hostname() + if err != nil { + t.Fatal(err) + } + serverName = fmt.Sprintf("%s/controller", serverName) + if err := tc.b.SetupEventing(tc.b.Logger, tc.b.StderrLock, serverName, base.WithEventerConfig(opts.Config.Eventing)); err != nil { + t.Fatal(err) + } if opts.WorkerStatusGracePeriodDuration != 0 { opts.Config.Controller.WorkerStatusGracePeriodDuration = opts.WorkerStatusGracePeriodDuration diff --git a/internal/daemon/controller/testing_test.go b/internal/daemon/controller/testing_test.go index 03d74ad448..a39e130b37 100644 --- a/internal/daemon/controller/testing_test.go +++ b/internal/daemon/controller/testing_test.go @@ -57,11 +57,11 @@ func Test_TestController(t *testing.T) { } assert.Empty(captureFn(func() { - tc := NewTestController(t, &TestControllerOpts{DisableEventing: true}) + tc := NewTestController(t, nil) defer tc.Shutdown() })) assert.NotEmpty(captureFn(func() { - tc := NewTestController(t, nil) + tc := NewTestController(t, &TestControllerOpts{EnableEventing: true}) defer tc.Shutdown() })) }) diff --git a/internal/daemon/worker/testing.go b/internal/daemon/worker/testing.go index f8d8debc09..79c17355be 100644 --- a/internal/daemon/worker/testing.go +++ b/internal/daemon/worker/testing.go @@ -226,6 +226,18 @@ type TestWorkerOpts struct { // Toggle worker auth debugging WorkerAuthDebuggingEnabled *atomic.Bool + + // Enable audit events + EnableAuditEvents bool + + // Enable system events + EnableSysEvents bool + + // Enable observation events + EnableObservationEvents bool + + // Enable error events + EnableErrorEvents bool } func NewTestWorker(t testing.TB, opts *TestWorkerOpts) *TestWorker { @@ -255,7 +267,12 @@ func NewTestWorker(t testing.TB, opts *TestWorkerOpts) *TestWorker { // Get dev config, or use a provided one var err error if opts.Config == nil { - opts.Config, err = config.DevWorker() + var configOpts []config.Option + configOpts = append(configOpts, config.WithAuditEventsEnabled(opts.EnableAuditEvents)) + configOpts = append(configOpts, config.WithSysEventsEnabled(opts.EnableSysEvents)) + configOpts = append(configOpts, config.WithObservationsEnabled(opts.EnableObservationEvents)) + configOpts = append(configOpts, config.TestWithErrorEventsEnabled(t, opts.EnableErrorEvents)) + opts.Config, err = config.DevWorker(configOpts...) if err != nil { t.Fatal(err) } diff --git a/internal/observability/event/context.go b/internal/observability/event/context.go index 567eef139f..c13bd2ee94 100644 --- a/internal/observability/event/context.go +++ b/internal/observability/event/context.go @@ -9,9 +9,14 @@ import ( "os" "runtime" "time" + + "github.com/hashicorp/boundary/internal/test" ) -var ci bool +var ( + ci bool + isTest bool +) type key int @@ -24,6 +29,7 @@ const ( func init() { ci = os.Getenv("CI") != "" + isTest = test.IsTestRun() } // NewEventerContext will return a context containing a value of the provided Eventer @@ -136,7 +142,10 @@ func WriteError(ctx context.Context, caller Op, e error, opt ...Option) { if !ok { eventer = SysEventer() if eventer == nil { - if !ci { + switch { + case !ci: + case !isTest: + default: fallbackLogger().Error(fmt.Sprintf("%s: no eventer available to write error: %v", op, e)) } return diff --git a/internal/observability/event/eventer.go b/internal/observability/event/eventer.go index 35d9822ce7..798d5c708f 100644 --- a/internal/observability/event/eventer.go +++ b/internal/observability/event/eventer.go @@ -661,6 +661,9 @@ func (e *Eventer) writeError(ctx context.Context, event *err, opt ...Option) err if event == nil { return fmt.Errorf("%s: missing event: %w", op, ErrInvalidParameter) } + if e.conf.ErrorEventsDisabled { + return nil + } opts := getOpts(opt...) if e.gated.Load() && !opts.withNoGateLocking { e.gatedQueueLock.Lock() @@ -689,6 +692,9 @@ func (e *Eventer) writeSysEvent(ctx context.Context, event *sysEvent, opt ...Opt if event == nil { return fmt.Errorf("%s: missing event: %w", op, ErrInvalidParameter) } + if !e.conf.SysEventsEnabled { + return nil + } opts := getOpts(opt...) if e.gated.Load() && !opts.withNoGateLocking { e.gatedQueueLock.Lock() diff --git a/internal/observability/event/eventer_config.go b/internal/observability/event/eventer_config.go index 22b46d55e4..0b2acdd1a6 100644 --- a/internal/observability/event/eventer_config.go +++ b/internal/observability/event/eventer_config.go @@ -13,6 +13,7 @@ type EventerConfig struct { 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 are all the configured sinks + ErrorEventsDisabled bool `hcl:"-"` // ErrorEventsDisabled will disable error events from being emitted. This should only be used to turn off error events in tests. } // Validate will Validate the config. A config isn't required to have any diff --git a/internal/observability/event/eventer_test.go b/internal/observability/event/eventer_test.go index 37195c02af..4067c02dd9 100644 --- a/internal/observability/event/eventer_test.go +++ b/internal/observability/event/eventer_test.go @@ -350,11 +350,11 @@ func TestEventer_writeError(t *testing.T) { func Test_NewEventer(t *testing.T) { t.Parallel() - testSetup := TestEventerConfig(t, "Test_NewEventer") + testSetup := TestEventerConfig(t, "Test_NewEventer", TestWithStderrSink(t)) - testSetupWithOpts := TestEventerConfig(t, "Test_NewEventer", TestWithAuditSink(t), TestWithObservationSink(t), TestWithSysSink(t)) + testSetupWithOpts := TestEventerConfig(t, "Test_NewEventer", TestWithStderrSink(t), TestWithAuditSink(t), TestWithObservationSink(t), TestWithSysSink(t)) - testHclogSetup := TestEventerConfig(t, "Test_NewEventer", testWithSinkFormat(t, TextHclogSinkFormat)) + testHclogSetup := TestEventerConfig(t, "Test_NewEventer", TestWithStderrSink(t), testWithSinkFormat(t, TextHclogSinkFormat)) testLock := &sync.Mutex{} testLogger := testLogger(t, testLock) diff --git a/internal/observability/event/options.go b/internal/observability/event/options.go index d61fd30285..7558656361 100644 --- a/internal/observability/event/options.go +++ b/internal/observability/event/options.go @@ -53,6 +53,7 @@ type options struct { withHclogLevel hclog.Level withBroker broker // test only option + withStderrSink bool // test only option withAuditSink bool // test only option withObservationSink bool // test only option withSysSink bool // test only option diff --git a/internal/observability/event/testing.go b/internal/observability/event/testing.go index d184720275..2ca6db0f38 100644 --- a/internal/observability/event/testing.go +++ b/internal/observability/event/testing.go @@ -89,6 +89,7 @@ func TestEventerConfig(t testing.TB, testName string, opt ...Option) TestConfig EventerConfig: EventerConfig{ ObservationsEnabled: true, AuditEnabled: true, + SysEventsEnabled: true, Sinks: []*SinkConfig{ { Name: "every-type-file-sink", @@ -101,13 +102,6 @@ func TestEventerConfig(t testing.TB, testName string, opt ...Option) TestConfig }, AuditConfig: DefaultAuditConfig(), }, - { - Name: "stderr", - Type: StderrSink, - EventTypes: []Type{EveryType}, - Format: opts.withSinkFormat, - AuditConfig: DefaultAuditConfig(), - }, { Name: "err-file-sink", Type: FileSink, @@ -123,6 +117,15 @@ func TestEventerConfig(t testing.TB, testName string, opt ...Option) TestConfig AllEvents: tmpAllFile, ErrorEvents: tmpErrFile, } + if opts.withStderrSink { + c.EventerConfig.Sinks = append(c.EventerConfig.Sinks, &SinkConfig{ + Name: "stderr", + Type: StderrSink, + EventTypes: []Type{EveryType}, + Format: opts.withSinkFormat, + AuditConfig: DefaultAuditConfig(), + }) + } if opts.withAuditSink { tmpFile, err := ioutil.TempFile("./", "tmp-audit-"+testName) require.NoError(err) @@ -259,6 +262,14 @@ func TestWithSysSink(t testing.TB) Option { } } +// TestWithStderrSink is a test option +func TestWithStderrSink(t testing.TB) Option { + t.Helper() + return func(o *options) { + o.withStderrSink = true + } +} + // withNoDefaultSink is an unexported test option func withNoDefaultSink(t testing.TB) Option { t.Helper() diff --git a/internal/test/test.go b/internal/test/test.go new file mode 100644 index 0000000000..4a8940f67b --- /dev/null +++ b/internal/test/test.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package test + +import ( + "flag" + "testing" +) + +func init() { + testing.Init() + flag.Parse() +} + +// IsTestRun will return whether or not we're operating within the context of a +// test. +func IsTestRun() bool { + // TODO jimlambrt 4/23 -> convert to go 1.21 built-in func when it's + // available in boundary: + // https://github.com/golang/go/commit/7f38067acb738c43d870400dd648662d31456f5f#diff-b3e6126779b5ec9d3d6cea7cc54054ba78f4d7a0a6248d9e458bbd9b3d72fce3 + + // just check if the test verbose (test.v) has been initialized... we don't + // care about it's value. + return flag.Lookup("test.v") != nil +} diff --git a/internal/test/test_test.go b/internal/test/test_test.go new file mode 100644 index 0000000000..d679bf0c7d --- /dev/null +++ b/internal/test/test_test.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package test + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_IsTestRun(t *testing.T) { + require.True(t, IsTestRun()) +}