From cb1cafcb6c9d3fa85a766473572defd91e36fbc0 Mon Sep 17 00:00:00 2001 From: Eric Chlebek Date: Thu, 25 Jul 2024 12:46:29 -0700 Subject: [PATCH] Add support for enabling and disabling ephemeral (#1638) This commit adds support for --enable-ephemeral and --disable-ephemeral. Like hostmetrics, ephemeral mode is enabled and disabled by linking a file to conf.d, or removing the link. Signed-off-by: Eric Chlebek --- pkg/tools/otelcol-config/action_context.go | 5 ++ pkg/tools/otelcol-config/ephemeral.go | 27 +++++++ pkg/tools/otelcol-config/ephemeral_test.go | 84 ++++++++++++++++++++++ pkg/tools/otelcol-config/flag_actions.go | 4 +- pkg/tools/otelcol-config/main.go | 39 +++++++--- 5 files changed, 147 insertions(+), 12 deletions(-) create mode 100644 pkg/tools/otelcol-config/ephemeral.go create mode 100644 pkg/tools/otelcol-config/ephemeral_test.go diff --git a/pkg/tools/otelcol-config/action_context.go b/pkg/tools/otelcol-config/action_context.go index 746bf9d81e..bc3855d392 100644 --- a/pkg/tools/otelcol-config/action_context.go +++ b/pkg/tools/otelcol-config/action_context.go @@ -7,6 +7,9 @@ import ( // actionContext provides an abstraction of I/O for all actions. The reason // for this to exist is so I/O operations can be mocked in test. +// +// actionContext is not like context.Context, and is not for cancellation or +// for carrying values across API boundaries. It is for abstracting I/O. type actionContext struct { ConfigDir fs.FS Flags *flagValues @@ -17,4 +20,6 @@ type actionContext struct { WriteSumologicRemote func([]byte) (int, error) LinkHostMetrics func() error UnlinkHostMetrics func() error + LinkEphemeral func() error + UnlinkEphemeral func() error } diff --git a/pkg/tools/otelcol-config/ephemeral.go b/pkg/tools/otelcol-config/ephemeral.go new file mode 100644 index 0000000000..f858bd5a4b --- /dev/null +++ b/pkg/tools/otelcol-config/ephemeral.go @@ -0,0 +1,27 @@ +package main + +import "errors" + +// EnableEphemeralAction links the available ephemeral configuration to conf.d +func EnableEphemeralAction(ctx *actionContext) error { + conf, err := ReadConfigDir(ctx.ConfigDir) + if err != nil { + return err + } + if conf.SumologicRemote != nil { + return errors.New("enable-ephemeral not supported for remote-controlled collectors") + } + return ctx.LinkEphemeral() +} + +// DisableEphemeralAction removes the link to the ephemeral configuration. +func DisableEphemeralAction(ctx *actionContext) error { + conf, err := ReadConfigDir(ctx.ConfigDir) + if err != nil { + return err + } + if conf.SumologicRemote != nil { + return errors.New("disable-ephemeral not supported for remote-controlled collectors") + } + return ctx.UnlinkEphemeral() +} diff --git a/pkg/tools/otelcol-config/ephemeral_test.go b/pkg/tools/otelcol-config/ephemeral_test.go new file mode 100644 index 0000000000..ad13ef4244 --- /dev/null +++ b/pkg/tools/otelcol-config/ephemeral_test.go @@ -0,0 +1,84 @@ +package main + +import ( + "errors" + "testing" + "testing/fstest" +) + +func TestEnableEphemeralAction(t *testing.T) { + var called bool + linkFunc := func() error { + called = true + return nil + } + errLinkFunc := func() error { + return errors.New("error") + } + ctx := &actionContext{ + ConfigDir: fstest.MapFS{}, + LinkEphemeral: linkFunc, + } + if err := EnableEphemeralAction(ctx); err != nil { + t.Fatal(err) + } + if !called { + t.Error("ephemeral linker not called") + } + ctx.LinkEphemeral = errLinkFunc + if err := EnableEphemeralAction(ctx); err == nil { + t.Fatal("expected non-nil error") + } +} + +func TestEnableEphemeralActionRemoteControlled(t *testing.T) { + ctx := &actionContext{ + ConfigDir: fstest.MapFS{ + SumologicRemoteDotYaml: &fstest.MapFile{ + Data: []byte(`{"extensions":{"opamp":{"enabled":true}}}`), + }, + }, + } + if err := EnableEphemeralAction(ctx); err == nil { + t.Fatal("expected non-nil error") + } +} + +func TestDisableEphemeralActionRemoteControlled(t *testing.T) { + ctx := &actionContext{ + ConfigDir: fstest.MapFS{ + SumologicRemoteDotYaml: &fstest.MapFile{ + Data: []byte(`{"extensions":{"opamp":{"enabled":true}}}`), + }, + }, + } + if err := DisableEphemeralAction(ctx); err == nil { + t.Fatal("expected non-nil error") + } +} + +func TestDisableEphemeralAction(t *testing.T) { + var called bool + unlinkFunc := func() error { + called = true + return nil + } + errUnlinkFunc := func() error { + return errors.New("error") + } + ctx := &actionContext{ + ConfigDir: fstest.MapFS{}, + UnlinkEphemeral: unlinkFunc, + } + if err := DisableEphemeralAction(ctx); err != nil { + t.Fatal(err) + } + if !called { + t.Error("hostmetrics unlinker not called") + } + ctx.UnlinkEphemeral = errUnlinkFunc + if err := DisableEphemeralAction(ctx); err == nil { + t.Fatal("expected non-nil error") + } + +} diff --git a/pkg/tools/otelcol-config/flag_actions.go b/pkg/tools/otelcol-config/flag_actions.go index c8ad2536d8..12cdb89c1a 100644 --- a/pkg/tools/otelcol-config/flag_actions.go +++ b/pkg/tools/otelcol-config/flag_actions.go @@ -20,8 +20,8 @@ var flagActions = map[string]action{ flagSetInstallationToken: SetInstallationTokenAction, flagEnableHostmetrics: EnableHostmetricsAction, flagDisableHostmetrics: DisableHostmetricsAction, - flagEnableEphemeral: notImplementedAction, - flagDisableEphemeral: notImplementedAction, + flagEnableEphemeral: EnableEphemeralAction, + flagDisableEphemeral: DisableEphemeralAction, flagSetAPIURL: notImplementedAction, flagEnableRemoteControl: EnableRemoteControlAction, flagDisableRemoteControl: DisableRemoteControlAction, diff --git a/pkg/tools/otelcol-config/main.go b/pkg/tools/otelcol-config/main.go index 42dfd72bb8..6562e491b0 100644 --- a/pkg/tools/otelcol-config/main.go +++ b/pkg/tools/otelcol-config/main.go @@ -15,6 +15,7 @@ import ( const ( hostmetricsLinux = "hostmetrics-linux.yaml" hostmetricsDarwin = "hostmetrics-darwin.yaml" + ephemeralYAML = "ephemeral.yaml" ) // errorCoder is here to give actions a way to set the exit status of the program @@ -112,12 +113,11 @@ func isLinkError(err error) bool { return linkError } -func getHostMetricsLinker(values *flagValues) func() error { - filename := getHostMetricsFilename() - hostmetricsAvailPath := filepath.Join(values.ConfigDir, ConfDotDAvailable, filename) - hostmetricsPath := filepath.Join(values.ConfigDir, ConfDotD, filename) +func getLinker(values *flagValues, filename string) func() error { + availPath := filepath.Join(values.ConfigDir, ConfDotDAvailable, filename) + configPath := filepath.Join(values.ConfigDir, ConfDotD, filename) return func() error { - if err := os.Symlink(hostmetricsAvailPath, hostmetricsPath); isLinkError(err) { + if err := os.Symlink(availPath, configPath); isLinkError(err) { // if the link already exists, hostmetrics are already enabled return nil } else { @@ -126,13 +126,12 @@ func getHostMetricsLinker(values *flagValues) func() error { } } -func getHostMetricsUnlinker(values *flagValues) func() error { - filename := getHostMetricsFilename() - hostmetricsPath := filepath.Join(values.ConfigDir, ConfDotD, filename) +func getUnlinker(values *flagValues, filename string) func() error { + configPath := filepath.Join(values.ConfigDir, ConfDotD, filename) return func() error { - err := os.Remove(hostmetricsPath) + err := os.Remove(configPath) var perr *fs.PathError - if errors.As(os.Remove(hostmetricsPath), &perr) && perr.Err == syscall.ENOENT { + if errors.As(err, &perr) && perr.Err == syscall.ENOENT { // if the link does not exist, hostmetrics are already disabled return nil } else { @@ -142,6 +141,24 @@ func getHostMetricsUnlinker(values *flagValues) func() error { } } +func getHostMetricsLinker(values *flagValues) func() error { + filename := getHostMetricsFilename() + return getLinker(values, filename) +} + +func getHostMetricsUnlinker(values *flagValues) func() error { + filename := getHostMetricsFilename() + return getUnlinker(values, filename) +} + +func getEphemeralLinker(values *flagValues) func() error { + return getLinker(values, ephemeralYAML) +} + +func getEphemeralUnlinker(values *flagValues) func() error { + return getUnlinker(values, ephemeralYAML) +} + // main. here is what it does: // // 1. Check basic OS compatibility @@ -177,6 +194,8 @@ func main() { WriteSumologicRemote: getSumologicRemoteWriter(flagValues), LinkHostMetrics: getHostMetricsLinker(flagValues), UnlinkHostMetrics: getHostMetricsUnlinker(flagValues), + LinkEphemeral: getEphemeralLinker(flagValues), + UnlinkEphemeral: getEphemeralUnlinker(flagValues), } if err := visitFlags(fs, ctx); err != nil {