From cb6f4ff66fd4e798b0ddeaa095362f6b887096be Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Mon, 17 Aug 2026 10:38:51 +0200 Subject: [PATCH 01/46] add precedence of configuration for splunk_outputs --- internal/conf/outputs.go | 55 ++++++++++++++----- internal/conf/outputs_test.go | 29 ++++++++-- internal/tabuilder/tabuilder.go | 79 +++++++++++++++++++++++----- internal/tabuilder/tabuilder_test.go | 56 ++++++++++++++++++++ pkg/splunkoutputsexporter/config.go | 7 +-- pkg/splunkoutputsexporter/factory.go | 16 +++--- 6 files changed, 202 insertions(+), 40 deletions(-) diff --git a/internal/conf/outputs.go b/internal/conf/outputs.go index 10b0f34..a3973b9 100644 --- a/internal/conf/outputs.go +++ b/internal/conf/outputs.go @@ -9,11 +9,10 @@ import ( "gopkg.in/ini.v1" ) -// ErrNoHTTPOut is returned by ReadOutputs when outputs.conf contains no [httpout] stanza. +// ErrNoHTTPOut is returned when outputs.conf contains no [httpout] stanza. var ErrNoHTTPOut = errors.New("no [httpout] stanza found in outputs.conf") // Output holds the settings from the [httpout] stanza in outputs.conf. -// outputs.conf supports exactly one [httpout] stanza. type Output struct { Token string URI string @@ -22,22 +21,54 @@ type Output struct { // Both require configuring BatcherConfig on the exporter helper. } -// ReadOutputs parses an outputs.conf payload and returns the [httpout] stanza. -// Returns ErrNoHTTPOut if no [httpout] stanza is present. -func ReadOutputs(payload []byte) (*Output, error) { +// ParseConf parses a single outputs.conf payload into a generic +// stanza -> key -> value map. All stanza names and keys are lower-cased. +func ParseConf(payload []byte) (map[string]map[string]string, error) { f, err := ini.Load(payload) if err != nil { return nil, err } - if !f.HasSection("httpout") { - return nil, ErrNoHTTPOut + result := make(map[string]map[string]string) + for _, section := range f.Sections() { + name := section.Name() + if name == ini.DefaultSection { + continue + } + keys := make(map[string]string) + for _, key := range section.Keys() { + keys[key.Name()] = key.Value() + } + result[name] = keys } - section, err := f.GetSection("httpout") - if err != nil { - return nil, err + return result, nil +} + +// MergeConf merges multiple parsed outputs.conf layers in order (lowest to +// highest precedence). Later layers override keys from earlier ones. +func MergeConf(layers []map[string]map[string]string) map[string]map[string]string { + merged := make(map[string]map[string]string) + for _, layer := range layers { + for stanza, keys := range layer { + if merged[stanza] == nil { + merged[stanza] = make(map[string]string) + } + for k, v := range keys { + merged[stanza][k] = v + } + } + } + return merged +} + +// HTTPOut extracts the [httpout] stanza from a merged conf map. +// Returns ErrNoHTTPOut if the stanza is absent. +func HTTPOut(merged map[string]map[string]string) (*Output, error) { + keys, ok := merged["httpout"] + if !ok { + return nil, ErrNoHTTPOut } return &Output{ - Token: section.Key("httpEventCollectorToken").String(), - URI: section.Key("uri").String(), + Token: keys["httpEventCollectorToken"], + URI: keys["uri"], }, nil } diff --git a/internal/conf/outputs_test.go b/internal/conf/outputs_test.go index 67fea62..2d9e96f 100644 --- a/internal/conf/outputs_test.go +++ b/internal/conf/outputs_test.go @@ -11,11 +11,14 @@ import ( "github.com/stretchr/testify/require" ) -func TestReadOutputs(t *testing.T) { +func TestParseConfAndHTTPOut(t *testing.T) { payload, err := os.ReadFile("testdata/outputs.conf") require.NoError(t, err) - output, err := ReadOutputs(payload) + parsed, err := ParseConf(payload) + require.NoError(t, err) + + output, err := HTTPOut(parsed) require.NoError(t, err) require.NotNil(t, output) @@ -23,7 +26,25 @@ func TestReadOutputs(t *testing.T) { assert.Equal(t, "https://splunk:8088/services/collector/event", output.URI) } -func TestReadOutputsNoHTTPOut(t *testing.T) { - _, err := ReadOutputs([]byte("[tcpout]\nserver = splunk:9997\n")) +func TestHTTPOutMissing(t *testing.T) { + parsed, err := ParseConf([]byte("[tcpout]\nserver = splunk:9997\n")) + require.NoError(t, err) + _, err = HTTPOut(parsed) require.ErrorIs(t, err, ErrNoHTTPOut) } + +func TestMergeConf(t *testing.T) { + base, err := ParseConf([]byte("[httpout]\nhttpEventCollectorToken = base-token\nuri = https://base:8088/services/collector/event\n")) + require.NoError(t, err) + + override, err := ParseConf([]byte("[httpout]\nhttpEventCollectorToken = override-token\n")) + require.NoError(t, err) + + merged := MergeConf([]map[string]map[string]string{base, override}) + output, err := HTTPOut(merged) + require.NoError(t, err) + + // override wins for token, base value kept for uri + assert.Equal(t, "override-token", output.Token) + assert.Equal(t, "https://base:8088/services/collector/event", output.URI) +} diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index ac96dc6..cfff856 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -14,6 +14,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter" "go.opentelemetry.io/collector/component" @@ -173,25 +174,77 @@ func ReadProps(baseDir string) ([]conf.Prop, error) { return conf.ReadProps(b) } -// ReadOutputs reads outputs.conf from baseDir, preferring local/ over default/. -// Returns the [httpout] stanza, or nil if no [httpout] stanza is present. -func ReadOutputs(baseDir string) (*conf.Output, error) { - fileToRead := filepath.Join(baseDir, "local", "outputs.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { - fileToRead = filepath.Join(baseDir, "default", "outputs.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { +// ReadOutputs discovers and merges outputs.conf files from splunkHome using +// standard Splunk precedence (lowest to highest): +// +// 1. etc/system/default/outputs.conf +// 2. etc/apps/*/default/outputs.conf (sorted by app name) +// 3. etc/apps/*/local/outputs.conf (sorted by app name) +// 4. etc/system/local/outputs.conf +// +// Returns the merged stanza -> key -> value map. Callers use HTTPOut (or +// future TCPOut, SyslogOut, etc.) to extract a specific output type. +func ReadOutputs(splunkHome string) (map[string]map[string]string, error) { + candidates := outputsConfPaths(splunkHome) + + var layers []map[string]map[string]string + for _, path := range candidates { + b, err := os.ReadFile(path) + if errors.Is(err, os.ErrNotExist) { + continue + } + if err != nil { return nil, err } + parsed, err := conf.ParseConf(b) + if err != nil { + return nil, fmt.Errorf("parse %s: %w", path, err) + } + layers = append(layers, parsed) } - b, err := os.ReadFile(fileToRead) + + return conf.MergeConf(layers), nil +} + +// HTTPOut extracts the [httpout] stanza from a merged outputs conf map. +// Returns conf.ErrNoHTTPOut if no [httpout] stanza is present. +func HTTPOut(merged map[string]map[string]string) (*conf.Output, error) { + return conf.HTTPOut(merged) +} + +// outputsConfPaths returns the ordered list of outputs.conf paths to consider, +// from lowest to highest precedence. +func outputsConfPaths(splunkHome string) []string { + etcDir := filepath.Join(splunkHome, "etc") + var paths []string + + // 1. system default + paths = append(paths, filepath.Join(etcDir, "system", "default", "outputs.conf")) + + // 2. app defaults (sorted by app name) + appDefaults, _ := filepath.Glob(filepath.Join(etcDir, "apps", "*", "default", "outputs.conf")) + sort.Strings(appDefaults) + paths = append(paths, appDefaults...) + + // 3. app locals (sorted by app name) + appLocals, _ := filepath.Glob(filepath.Join(etcDir, "apps", "*", "local", "outputs.conf")) + sort.Strings(appLocals) + paths = append(paths, appLocals...) + + // 4. system local (highest precedence) + paths = append(paths, filepath.Join(etcDir, "system", "local", "outputs.conf")) + + return paths +} + +// CreateExporter builds a logs exporter from a merged outputs conf map. +// It extracts [httpout] and builds a HEC exporter. Future output types +// (tcpout, syslog, etc.) will be added here as additional cases. +func CreateExporter(merged map[string]map[string]string, logger *zap.Logger, telemetrySettings component.TelemetrySettings) (exporter.Logs, error) { + output, err := conf.HTTPOut(merged) if err != nil { return nil, err } - return conf.ReadOutputs(b) -} - -// CreateExporter builds a logs exporter from the [httpout] stanza. -func CreateExporter(output *conf.Output, logger *zap.Logger, telemetrySettings component.TelemetrySettings) (exporter.Logs, error) { return newHECExporter(output, logger, telemetrySettings) } diff --git a/internal/tabuilder/tabuilder_test.go b/internal/tabuilder/tabuilder_test.go index 5c10537..356c68a 100644 --- a/internal/tabuilder/tabuilder_test.go +++ b/internal/tabuilder/tabuilder_test.go @@ -7,9 +7,65 @@ import ( "path/filepath" "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/splunk/tarunner/internal/conf" ) +func TestReadOutputs(t *testing.T) { + rootDir := filepath.Join("testdata", "outputs") + tests := []struct { + name string + splunkHome string + expectedToken string + expectedURI string + expectedErr error + }{ + { + name: "system_default_only", + splunkHome: filepath.Join(rootDir, "system_default"), + expectedToken: "token-system-default", + expectedURI: "https://system-default:8088/services/collector/event", + }, + { + name: "app_overrides_system_default", + splunkHome: filepath.Join(rootDir, "app_overrides"), + expectedToken: "token-app-default", + expectedURI: "https://app-local:8088/services/collector/event", + }, + { + name: "system_local_wins_over_app", + splunkHome: filepath.Join(rootDir, "system_local_wins"), + expectedToken: "token-system-local", + expectedURI: "https://system-local:8088/services/collector/event", + }, + { + name: "no_httpout", + splunkHome: filepath.Join(rootDir, "no_httpout"), + expectedErr: conf.ErrNoHTTPOut, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + merged, err := ReadOutputs(test.splunkHome) + if test.expectedErr != nil { + require.NoError(t, err) // ReadOutputs itself doesn't error on missing stanza + _, httpErr := HTTPOut(merged) + require.ErrorIs(t, httpErr, test.expectedErr) + return + } + require.NoError(t, err) + output, err := HTTPOut(merged) + require.NoError(t, err) + require.NotNil(t, output) + assert.Equal(t, test.expectedToken, output.Token) + assert.Equal(t, test.expectedURI, output.URI) + }) + } +} + func TestReadTransforms(t *testing.T) { rootDir := filepath.Join("testdata", "transforms") tests := []struct { diff --git a/pkg/splunkoutputsexporter/config.go b/pkg/splunkoutputsexporter/config.go index 7604ed1..23e19ef 100644 --- a/pkg/splunkoutputsexporter/config.go +++ b/pkg/splunkoutputsexporter/config.go @@ -5,7 +5,8 @@ package splunkoutputsexporter // Config holds the configuration for the splunk_outputs exporter. type Config struct { - // BaseDir is the directory containing local/ or default/ subdirectories - // with outputs.conf. Defaults to $SPLUNK_HOME if empty. - BaseDir string `mapstructure:"path"` + // BaseDir is the Splunk installation root. outputs.conf is discovered + // across etc/system/default, etc/apps/*, and etc/system/local using + // standard Splunk precedence. Overrides $SPLUNK_HOME when set. + BaseDir string `mapstructure:"base_dir"` } diff --git a/pkg/splunkoutputsexporter/factory.go b/pkg/splunkoutputsexporter/factory.go index f51043d..45f9227 100644 --- a/pkg/splunkoutputsexporter/factory.go +++ b/pkg/splunkoutputsexporter/factory.go @@ -29,20 +29,20 @@ func createDefaultConfig() component.Config { func createLogsFunc(_ context.Context, settings exporter.Settings, config component.Config) (exporter.Logs, error) { cfg := config.(Config) - baseDir := cfg.BaseDir - if baseDir == "" { - baseDir = os.Getenv("SPLUNK_HOME") + splunkHome := cfg.BaseDir + if splunkHome == "" { + splunkHome = os.Getenv("SPLUNK_HOME") } - if baseDir == "" { - return nil, fmt.Errorf("splunk_outputs: path is not set and SPLUNK_HOME is not defined") + if splunkHome == "" { + return nil, fmt.Errorf("splunk_outputs: base_dir is not set and SPLUNK_HOME is not defined") } - output, err := tabuilder.ReadOutputs(baseDir) + merged, err := tabuilder.ReadOutputs(splunkHome) if err != nil { - return nil, fmt.Errorf("splunk_outputs: %w (base_dir: %s)", err, baseDir) + return nil, fmt.Errorf("splunk_outputs: %w (base_dir: %s)", err, splunkHome) } - exp, err := tabuilder.CreateExporter(output, settings.Logger, settings.TelemetrySettings) + exp, err := tabuilder.CreateExporter(merged, settings.Logger, settings.TelemetrySettings) if err != nil { return nil, fmt.Errorf("splunk_outputs: %w", err) } From 15f9135cc78a541f184fb5d512dbc54fc896f2da Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Mon, 17 Aug 2026 15:11:00 +0200 Subject: [PATCH 02/46] add precedence of configuration for splunk_inputs --- internal/collector/collector_test.go | 16 ++-- internal/conf/inputs.go | 72 +++++++++++++++- internal/conf/inputs_test.go | 6 +- internal/conf/props.go | 20 +++++ internal/conf/transforms.go | 18 ++++ internal/script/command.go | 6 ++ internal/tabuilder/tabuilder.go | 121 ++++++++++++++++++--------- internal/tabuilder/tabuilder_test.go | 12 +-- pkg/splunkinputsreceiver/config.go | 5 +- pkg/splunkinputsreceiver/receiver.go | 19 +++-- 10 files changed, 230 insertions(+), 65 deletions(-) diff --git a/internal/collector/collector_test.go b/internal/collector/collector_test.go index a62ea28..8e4fd90 100644 --- a/internal/collector/collector_test.go +++ b/internal/collector/collector_test.go @@ -37,7 +37,7 @@ func TestRunTA(t *testing.T) { defer func() { _ = rcvr.Shutdown(context.Background()) }() - cancel, err := Run(filepath.Join("testdata", "ta"), &config.Config{ + cancel, err := Run(filepath.Join("testdata", "ta_home"), &config.Config{ Type: "otlp_http", Endpoint: "http://localhost:1337", }) @@ -60,7 +60,7 @@ func TestRunPeriodic(t *testing.T) { defer func() { _ = rcvr.Shutdown(context.Background()) }() - cancel, err := Run(filepath.Join("testdata", "periodic"), &config.Config{ + cancel, err := Run(filepath.Join("testdata", "periodic_home"), &config.Config{ Type: "otlp_http", Endpoint: "http://localhost:1338", }) @@ -101,7 +101,7 @@ func TestRunDisabled(t *testing.T) { defer func() { _ = rcvr.Shutdown(context.Background()) }() - cancel, err := Run(filepath.Join("testdata", "disabled"), &config.Config{ + cancel, err := Run(filepath.Join("testdata", "disabled_home"), &config.Config{ Type: "otlp_http", Endpoint: "http://localhost:1339", }) @@ -123,7 +123,7 @@ func TestRunDisabledInterval(t *testing.T) { defer func() { _ = rcvr.Shutdown(context.Background()) }() - cancel, err := Run(filepath.Join("testdata", "disabled_interval"), &config.Config{ + cancel, err := Run(filepath.Join("testdata", "disabled_interval_home"), &config.Config{ Type: "otlp_http", Endpoint: "http://localhost:1340", }) @@ -144,7 +144,7 @@ func TestRunScriptedInputs(t *testing.T) { defer func() { _ = rcvr.Shutdown(context.Background()) }() - cancel, err := Run(filepath.Join("testdata", "script"), &config.Config{ + cancel, err := Run(filepath.Join("testdata", "script_home"), &config.Config{ Type: "otlp_http", Endpoint: "http://localhost:1341", }) @@ -157,7 +157,7 @@ func TestRunScriptedInputs(t *testing.T) { } func TestUseTCP(t *testing.T) { - rootDir := filepath.Join("testdata", "tcp") + rootDir := filepath.Join("testdata", "tcp_home") logsSink := &consumertest.LogsSink{} cfg := otlpreceiver.NewFactory().CreateDefaultConfig().(*otlpreceiver.Config) cfg.Protocols.HTTP.GetOrInsertDefault().ServerConfig.NetAddr.Endpoint = "localhost:1342" @@ -190,7 +190,7 @@ func TestUseTCP(t *testing.T) { } func TestUseUDP(t *testing.T) { - rootDir := filepath.Join("testdata", "udp") + rootDir := filepath.Join("testdata", "udp_home") logsSink := &consumertest.LogsSink{} cfg := otlpreceiver.NewFactory().CreateDefaultConfig().(*otlpreceiver.Config) cfg.Protocols.HTTP.GetOrInsertDefault().ServerConfig.NetAddr.Endpoint = "localhost:1343" @@ -232,7 +232,7 @@ func TestRunScriptedInputsWithHEC(t *testing.T) { defer func() { _ = rcvr.Shutdown(context.Background()) }() - cancel, err := Run(filepath.Join("testdata", "script"), &config.Config{ + cancel, err := Run(filepath.Join("testdata", "script_home"), &config.Config{ Endpoint: "http://localhost:1341", Token: "foo", }) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index 020db16..bec81a0 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -5,6 +5,10 @@ package conf import ( "encoding/xml" + "fmt" + "path/filepath" + "runtime" + "strings" "gopkg.in/ini.v1" ) @@ -49,7 +53,10 @@ type Param struct { Value string `xml:",innerxml"` } -func ReadInput(payload []byte) ([]Input, error) { +// ReadInput parses an inputs.conf payload. appDir is the app directory +// (e.g. $SPLUNK_HOME/etc/apps/my_ta) used to resolve relative script paths +// to absolute paths at parse time. Pass an empty string to skip resolution. +func ReadInput(payload []byte, appDir string) ([]Input, error) { f, err := ini.Load(payload) if err != nil { return nil, err @@ -60,10 +67,14 @@ func ReadInput(payload []byte) ([]Input, error) { if section.Name() == ini.DefaultSection { continue // disregard default section. We need a stanza per input. } + name := section.Name() + if appDir != "" { + name = resolveInputPath(name, appDir) + } i := Input{ Configuration: Configuration{ Stanza: Stanza{ - Name: section.Name(), + Name: name, App: appName, Params: make([]Param, len(section.Keys())), }, @@ -84,6 +95,63 @@ func ReadInput(payload []byte) ([]Input, error) { return result, nil } +// resolveInputPath rewrites a stanza name's relative path component to an +// absolute path using appDir. Only script:// and bare (scripted) stanzas +// are rewritten; monitor://, tcp://, udp://, wineventlog:// are left as-is +// since their targets are not relative to the app directory. +func resolveInputPath(name, appDir string) string { + switch { + case hasScheme(name, "script"): + rel := name[len("script://"):] + return "script://" + absPath(appDir, rel) + case hasScheme(name, "monitor"), hasScheme(name, "tcp"), hasScheme(name, "udp"), hasScheme(name, "wineventlog"), hasScheme(name, "batch"): + return name + default: + // bare scripted input: name is the script filename + abs := absPath(appDir, filepath.Join("bin", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), name)) + return abs + } +} + +func hasScheme(name, scheme string) bool { + prefix := scheme + "://" + if len(name) < len(prefix) { + return false + } + return strings.EqualFold(name[:len(prefix)], prefix) +} + +func absPath(appDir, rel string) string { + if filepath.IsAbs(rel) { + return rel + } + abs, err := filepath.Abs(filepath.Join(appDir, rel)) + if err != nil { + return filepath.Join(appDir, rel) + } + return abs +} + +// MergeInputs merges multiple slices of inputs, with later slices taking +// precedence over earlier ones (local/ wins over default/). +// Stanzas are keyed by name; the last definition of a stanza wins entirely. +func MergeInputs(layers [][]Input) []Input { + seen := make(map[string]int) + var result []Input + for _, layer := range layers { + for _, input := range layer { + name := input.Configuration.Stanza.Name + if idx, ok := seen[name]; ok { + result[idx] = input + } else { + seen[name] = len(result) + result = append(result, input) + } + } + } + return result +} + func (i *Input) ToXML() ([]byte, error) { b, err := xml.MarshalIndent(i, "", " ") return append([]byte(xmlDeclaration), b...), err diff --git a/internal/conf/inputs_test.go b/internal/conf/inputs_test.go index 6f16bdb..38048db 100644 --- a/internal/conf/inputs_test.go +++ b/internal/conf/inputs_test.go @@ -15,7 +15,7 @@ import ( func TestOneInput(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", "oneinput.conf")) require.NoError(t, err) - res, err := ReadInput(b) + res, err := ReadInput(b, "") require.NoError(t, err) assert.Equal( t, @@ -50,7 +50,7 @@ func TestOneInput(t *testing.T) { func TestTwoInputs(t *testing.T) { b, err := os.ReadFile(filepath.Join("testdata", "twoinputs.conf")) require.NoError(t, err) - res, err := ReadInput(b) + res, err := ReadInput(b, "") require.NoError(t, err) assert.Equal(t, []Input{{ ServerHost: "", @@ -133,7 +133,7 @@ func TestToXML(t *testing.T) { ` b, err := os.ReadFile(filepath.Join("testdata", "oneinput.conf")) require.NoError(t, err) - res, err := ReadInput(b) + res, err := ReadInput(b, "") require.NoError(t, err) assert.Len(t, res, 1) res[0].ServerHost = "773c28971b2a" diff --git a/internal/conf/props.go b/internal/conf/props.go index 4598ee2..acf81fd 100644 --- a/internal/conf/props.go +++ b/internal/conf/props.go @@ -60,6 +60,26 @@ func (p *Prop) Type() PropType { } } +// MergeProps merges multiple slices of props, with later slices taking +// precedence. Stanzas are keyed by name; the last definition wins. +// The merged result is re-ordered by specificity. +func MergeProps(layers [][]Prop) []Prop { + seen := make(map[string]int) + var result []Prop + for _, layer := range layers { + for _, p := range layer { + if idx, ok := seen[p.Name]; ok { + result[idx] = p + } else { + seen[p.Name] = len(result) + result = append(result, p) + } + } + } + orderProps(result) + return result +} + func ReadProps(payload []byte) ([]Prop, error) { f, err := ini.Load(payload) if err != nil { diff --git a/internal/conf/transforms.go b/internal/conf/transforms.go index eb10cd5..5d58369 100644 --- a/internal/conf/transforms.go +++ b/internal/conf/transforms.go @@ -11,6 +11,24 @@ type Transform struct { Format string } +// MergeTransforms merges multiple slices of transforms, with later slices +// taking precedence. Stanzas are keyed by name; the last definition wins. +func MergeTransforms(layers [][]Transform) []Transform { + seen := make(map[string]int) + var result []Transform + for _, layer := range layers { + for _, t := range layer { + if idx, ok := seen[t.Name]; ok { + result[idx] = t + } else { + seen[t.Name] = len(result) + result = append(result, t) + } + } + } + return result +} + func ReadTransforms(payload []byte) ([]Transform, error) { f, err := ini.Load(payload) if err != nil { diff --git a/internal/script/command.go b/internal/script/command.go index 2b85487..da470ce 100644 --- a/internal/script/command.go +++ b/internal/script/command.go @@ -22,8 +22,14 @@ func DetermineCommandName(baseDir string, input conf.Input) (string, error) { case "monitor", "batch": return parsed.Target, nil case "script": + if filepath.IsAbs(parsed.Target) { + return parsed.Target, nil + } return GetPath(baseDir, parsed.Target) case "": + if filepath.IsAbs(parsed.Target) { + return parsed.Target, nil + } return GetPath(baseDir, filepath.Join("bin", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), parsed.Target)) default: return "", fmt.Errorf("unknown scheme %q", parsed.Kind) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index cfff856..6204b5c 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -39,8 +39,8 @@ import ( ) // CreateReceivers builds a logs receiver for every enabled input stanza, -// dispatching by the stanza name's input kind. Stanzas with disabled=1 are -// skipped. +// dispatching by the stanza name's input kind. Stanzas with disabled=1 or +// unsupported kinds are skipped silently. func CreateReceivers(ctx context.Context, inputs []conf.Input, transforms []conf.Transform, props []conf.Prop, baseDir string, next consumer.Logs, telemetrySettings component.TelemetrySettings) ([]receiver.Logs, error) { var receivers []receiver.Logs for _, input := range inputs { @@ -52,12 +52,16 @@ func CreateReceivers(ctx context.Context, inputs []conf.Input, transforms []conf if err != nil { return nil, fmt.Errorf("failed to create receiver %q: %w", input.Configuration.Stanza.Name, err) } + if l == nil { + continue + } receivers = append(receivers, l) } return receivers, nil } // CreateReceiver builds a single logs receiver for an input stanza. +// Returns nil (no error) for unsupported input kinds. func CreateReceiver(ctx context.Context, baseDir string, next consumer.Logs, input conf.Input, transforms []conf.Transform, props []conf.Prop, telemetrySettings component.TelemetrySettings) (receiver.Logs, error) { parsed, err := stanza.ParseName(input.Configuration.Stanza.Name) if err != nil { @@ -113,7 +117,8 @@ func CreateReceiver(ctx context.Context, baseDir string, next consumer.Logs, inp Props: props, }, next) default: - return nil, fmt.Errorf("unsupported scheme %q", parsed.Kind) + // unsupported kind — skip silently + return nil, nil } } @@ -124,54 +129,90 @@ func settings(f receiver.Factory, path string, telemetrySettings component.Telem } } -// ReadInputs reads inputs.conf, preferring local/ over default/. -func ReadInputs(baseDir string) ([]conf.Input, error) { - fileToRead := filepath.Join(baseDir, "local", "inputs.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { - fileToRead = filepath.Join(baseDir, "default", "inputs.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { +// ReadInputs discovers and merges inputs.conf files from splunkHome using +// standard Splunk precedence (lowest to highest): +// +// 1. etc/apps/*/default/inputs.conf (sorted by app name) +// 2. etc/apps/*/local/inputs.conf (sorted by app name) +// +// Stanzas are merged by name; local/ wins over default/ for the same app, +// and later apps (alphabetically) win over earlier ones within the same tier. +// Relative script paths are resolved to absolute paths at read time. +func ReadInputs(splunkHome string) ([]conf.Input, error) { + etcApps := filepath.Join(splunkHome, "etc", "apps") + + appDefaults, _ := filepath.Glob(filepath.Join(etcApps, "*", "default", "inputs.conf")) + sort.Strings(appDefaults) + appLocals, _ := filepath.Glob(filepath.Join(etcApps, "*", "local", "inputs.conf")) + sort.Strings(appLocals) + + var layers [][]conf.Input + for _, path := range append(appDefaults, appLocals...) { + appDir := filepath.Dir(filepath.Dir(path)) // strip /default or /local + b, err := os.ReadFile(path) + if err != nil { return nil, err } + inputs, err := conf.ReadInput(b, appDir) + if err != nil { + return nil, fmt.Errorf("parse %s: %w", path, err) + } + layers = append(layers, inputs) } - b, err := os.ReadFile(fileToRead) - if err != nil { - return nil, err - } - return conf.ReadInput(b) + + return conf.MergeInputs(layers), nil } -// ReadTransforms reads transforms.conf, preferring local/ over default/. It -// returns a nil slice (and no error) when the file is absent. -func ReadTransforms(baseDir string) ([]conf.Transform, error) { - fileToRead := filepath.Join(baseDir, "local", "transforms.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { - fileToRead = filepath.Join(baseDir, "default", "transforms.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { - return nil, nil +// ReadTransforms discovers and merges transforms.conf files from splunkHome +// using standard Splunk precedence. Returns nil (no error) when absent. +func ReadTransforms(splunkHome string) ([]conf.Transform, error) { + etcApps := filepath.Join(splunkHome, "etc", "apps") + + appDefaults, _ := filepath.Glob(filepath.Join(etcApps, "*", "default", "transforms.conf")) + sort.Strings(appDefaults) + appLocals, _ := filepath.Glob(filepath.Join(etcApps, "*", "local", "transforms.conf")) + sort.Strings(appLocals) + + var layers [][]conf.Transform + for _, path := range append(appDefaults, appLocals...) { + b, err := os.ReadFile(path) + if err != nil { + return nil, err } + transforms, err := conf.ReadTransforms(b) + if err != nil { + return nil, fmt.Errorf("parse %s: %w", path, err) + } + layers = append(layers, transforms) } - b, err := os.ReadFile(fileToRead) - if err != nil { - return nil, err - } - return conf.ReadTransforms(b) + + return conf.MergeTransforms(layers), nil } -// ReadProps reads props.conf, preferring local/ over default/. It returns a nil -// slice (and no error) when the file is absent. -func ReadProps(baseDir string) ([]conf.Prop, error) { - fileToRead := filepath.Join(baseDir, "local", "props.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { - fileToRead = filepath.Join(baseDir, "default", "props.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { - return nil, nil +// ReadProps discovers and merges props.conf files from splunkHome using +// standard Splunk precedence. Returns nil (no error) when absent. +func ReadProps(splunkHome string) ([]conf.Prop, error) { + etcApps := filepath.Join(splunkHome, "etc", "apps") + + appDefaults, _ := filepath.Glob(filepath.Join(etcApps, "*", "default", "props.conf")) + sort.Strings(appDefaults) + appLocals, _ := filepath.Glob(filepath.Join(etcApps, "*", "local", "props.conf")) + sort.Strings(appLocals) + + var layers [][]conf.Prop + for _, path := range append(appDefaults, appLocals...) { + b, err := os.ReadFile(path) + if err != nil { + return nil, err } + props, err := conf.ReadProps(b) + if err != nil { + return nil, fmt.Errorf("parse %s: %w", path, err) + } + layers = append(layers, props) } - b, err := os.ReadFile(fileToRead) - if err != nil { - return nil, err - } - return conf.ReadProps(b) + + return conf.MergeProps(layers), nil } // ReadOutputs discovers and merges outputs.conf files from splunkHome using diff --git a/internal/tabuilder/tabuilder_test.go b/internal/tabuilder/tabuilder_test.go index 356c68a..99bb769 100644 --- a/internal/tabuilder/tabuilder_test.go +++ b/internal/tabuilder/tabuilder_test.go @@ -70,33 +70,33 @@ func TestReadTransforms(t *testing.T) { rootDir := filepath.Join("testdata", "transforms") tests := []struct { name string - path string + splunkHome string expectedName string expectedRegex string }{ { name: "default", - path: filepath.Join(rootDir, "default"), + splunkHome: filepath.Join(rootDir, "splunk_default"), expectedName: "example_default", expectedRegex: "default", }, { name: "local", - path: filepath.Join(rootDir, "local"), + splunkHome: filepath.Join(rootDir, "splunk_local"), expectedName: "example_local", expectedRegex: "local", }, { name: "both", - path: filepath.Join(rootDir, "both"), - expectedName: "example_local2", + splunkHome: filepath.Join(rootDir, "splunk_both"), + expectedName: "example_transform", expectedRegex: "local", }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - transforms, err := ReadTransforms(test.path) + transforms, err := ReadTransforms(test.splunkHome) require.NoError(t, err) require.Len(t, transforms, 1) require.Equal(t, test.expectedName, transforms[0].Name) diff --git a/pkg/splunkinputsreceiver/config.go b/pkg/splunkinputsreceiver/config.go index 50fe229..337fca9 100644 --- a/pkg/splunkinputsreceiver/config.go +++ b/pkg/splunkinputsreceiver/config.go @@ -4,5 +4,8 @@ package splunkinputsreceiver type Config struct { - BaseDir string `mapstructure:"path"` + // BaseDir is the Splunk installation root ($SPLUNK_HOME). inputs.conf, + // transforms.conf, and props.conf are discovered across etc/apps/* using + // standard Splunk precedence. Overrides $SPLUNK_HOME when set. + BaseDir string `mapstructure:"base_dir"` } diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index 7b0e6d1..920a702 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -6,6 +6,8 @@ package splunkinputsreceiver import ( "context" "errors" + "fmt" + "os" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" @@ -56,21 +58,28 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { func createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - baseDir := cfg.BaseDir - inputs, err := tabuilder.ReadInputs(baseDir) + splunkHome := cfg.BaseDir + if splunkHome == "" { + splunkHome = os.Getenv("SPLUNK_HOME") + } + if splunkHome == "" { + return nil, fmt.Errorf("splunk_inputs: base_dir is not set and SPLUNK_HOME is not defined") + } + + inputs, err := tabuilder.ReadInputs(splunkHome) if err != nil { return nil, err } - transforms, err := tabuilder.ReadTransforms(baseDir) + transforms, err := tabuilder.ReadTransforms(splunkHome) if err != nil { return nil, err } - props, err := tabuilder.ReadProps(baseDir) + props, err := tabuilder.ReadProps(splunkHome) if err != nil { return nil, err } - receivers, err := tabuilder.CreateReceivers(ctx, inputs, transforms, props, baseDir, logs, settings.TelemetrySettings) + receivers, err := tabuilder.CreateReceivers(ctx, inputs, transforms, props, splunkHome, logs, settings.TelemetrySettings) if err != nil { return nil, err } From 07dcb0c58a2a1263669248870b080b5d9cb0543a Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Mon, 17 Aug 2026 16:14:17 +0200 Subject: [PATCH 03/46] simplify, use the same filesystem for both inputs and outputs --- internal/conf/outputs.go | 20 ++-- internal/conf/outputs_test.go | 2 +- internal/tabuilder/tabuilder.go | 164 ++++++++++++++------------------ 3 files changed, 84 insertions(+), 102 deletions(-) diff --git a/internal/conf/outputs.go b/internal/conf/outputs.go index a3973b9..3a2f31d 100644 --- a/internal/conf/outputs.go +++ b/internal/conf/outputs.go @@ -12,6 +12,9 @@ import ( // ErrNoHTTPOut is returned when outputs.conf contains no [httpout] stanza. var ErrNoHTTPOut = errors.New("no [httpout] stanza found in outputs.conf") +// ConfMap is a parsed .conf file: stanza name -> key -> value. +type ConfMap map[string]map[string]string + // Output holds the settings from the [httpout] stanza in outputs.conf. type Output struct { Token string @@ -23,12 +26,13 @@ type Output struct { // ParseConf parses a single outputs.conf payload into a generic // stanza -> key -> value map. All stanza names and keys are lower-cased. -func ParseConf(payload []byte) (map[string]map[string]string, error) { +// ParseConf parses a single .conf payload into a ConfMap. +func ParseConf(payload []byte) (ConfMap, error) { f, err := ini.Load(payload) if err != nil { return nil, err } - result := make(map[string]map[string]string) + result := make(ConfMap) for _, section := range f.Sections() { name := section.Name() if name == ini.DefaultSection { @@ -43,10 +47,10 @@ func ParseConf(payload []byte) (map[string]map[string]string, error) { return result, nil } -// MergeConf merges multiple parsed outputs.conf layers in order (lowest to -// highest precedence). Later layers override keys from earlier ones. -func MergeConf(layers []map[string]map[string]string) map[string]map[string]string { - merged := make(map[string]map[string]string) +// MergeConf merges multiple ConfMap layers in order (lowest to highest +// precedence). Later layers override keys from earlier ones. +func MergeConf(layers []ConfMap) ConfMap { + merged := make(ConfMap) for _, layer := range layers { for stanza, keys := range layer { if merged[stanza] == nil { @@ -60,9 +64,9 @@ func MergeConf(layers []map[string]map[string]string) map[string]map[string]stri return merged } -// HTTPOut extracts the [httpout] stanza from a merged conf map. +// HTTPOut extracts the [httpout] stanza from a ConfMap. // Returns ErrNoHTTPOut if the stanza is absent. -func HTTPOut(merged map[string]map[string]string) (*Output, error) { +func HTTPOut(merged ConfMap) (*Output, error) { keys, ok := merged["httpout"] if !ok { return nil, ErrNoHTTPOut diff --git a/internal/conf/outputs_test.go b/internal/conf/outputs_test.go index 2d9e96f..c5074c0 100644 --- a/internal/conf/outputs_test.go +++ b/internal/conf/outputs_test.go @@ -40,7 +40,7 @@ func TestMergeConf(t *testing.T) { override, err := ParseConf([]byte("[httpout]\nhttpEventCollectorToken = override-token\n")) require.NoError(t, err) - merged := MergeConf([]map[string]map[string]string{base, override}) + merged := MergeConf([]ConfMap{base, override}) output, err := HTTPOut(merged) require.NoError(t, err) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 6204b5c..9ebc578 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -129,159 +129,137 @@ func settings(f receiver.Factory, path string, telemetrySettings component.Telem } } -// ReadInputs discovers and merges inputs.conf files from splunkHome using -// standard Splunk precedence (lowest to highest): -// -// 1. etc/apps/*/default/inputs.conf (sorted by app name) -// 2. etc/apps/*/local/inputs.conf (sorted by app name) +// confFilePaths returns the ordered list of paths for a given .conf filename +// across splunkHome, from lowest to highest precedence: // -// Stanzas are merged by name; local/ wins over default/ for the same app, -// and later apps (alphabetically) win over earlier ones within the same tier. -// Relative script paths are resolved to absolute paths at read time. -func ReadInputs(splunkHome string) ([]conf.Input, error) { - etcApps := filepath.Join(splunkHome, "etc", "apps") +// 1. etc/system/default/ +// 2. etc/apps/*/default/ (sorted by app name) +// 3. etc/apps/*/local/ (sorted by app name) +// 4. etc/system/local/ +func confFilePaths(splunkHome, filename string) []string { + etcDir := filepath.Join(splunkHome, "etc") + var paths []string + + paths = append(paths, filepath.Join(etcDir, "system", "default", filename)) - appDefaults, _ := filepath.Glob(filepath.Join(etcApps, "*", "default", "inputs.conf")) + appDefaults, _ := filepath.Glob(filepath.Join(etcDir, "apps", "*", "default", filename)) sort.Strings(appDefaults) - appLocals, _ := filepath.Glob(filepath.Join(etcApps, "*", "local", "inputs.conf")) + paths = append(paths, appDefaults...) + + appLocals, _ := filepath.Glob(filepath.Join(etcDir, "apps", "*", "local", filename)) sort.Strings(appLocals) + paths = append(paths, appLocals...) + + paths = append(paths, filepath.Join(etcDir, "system", "local", filename)) + + return paths +} + +// readConfFiles reads and skips missing files from a list of paths, returning +// raw payloads in order. +func readConfFiles(paths []string) ([][]byte, error) { + var payloads [][]byte + for _, path := range paths { + b, err := os.ReadFile(path) + if errors.Is(err, os.ErrNotExist) { + continue + } + if err != nil { + return nil, err + } + payloads = append(payloads, b) + } + return payloads, nil +} +// ReadInputs discovers and merges inputs.conf files from splunkHome using +// standard Splunk precedence. Relative script paths are resolved to absolute +// paths at read time. +func ReadInputs(splunkHome string) ([]conf.Input, error) { var layers [][]conf.Input - for _, path := range append(appDefaults, appLocals...) { - appDir := filepath.Dir(filepath.Dir(path)) // strip /default or /local + for _, path := range confFilePaths(splunkHome, "inputs.conf") { b, err := os.ReadFile(path) + if errors.Is(err, os.ErrNotExist) { + continue + } if err != nil { return nil, err } + appDir := filepath.Dir(filepath.Dir(path)) // strip /default or /local inputs, err := conf.ReadInput(b, appDir) if err != nil { return nil, fmt.Errorf("parse %s: %w", path, err) } layers = append(layers, inputs) } - return conf.MergeInputs(layers), nil } // ReadTransforms discovers and merges transforms.conf files from splunkHome // using standard Splunk precedence. Returns nil (no error) when absent. func ReadTransforms(splunkHome string) ([]conf.Transform, error) { - etcApps := filepath.Join(splunkHome, "etc", "apps") - - appDefaults, _ := filepath.Glob(filepath.Join(etcApps, "*", "default", "transforms.conf")) - sort.Strings(appDefaults) - appLocals, _ := filepath.Glob(filepath.Join(etcApps, "*", "local", "transforms.conf")) - sort.Strings(appLocals) - + payloads, err := readConfFiles(confFilePaths(splunkHome, "transforms.conf")) + if err != nil { + return nil, err + } var layers [][]conf.Transform - for _, path := range append(appDefaults, appLocals...) { - b, err := os.ReadFile(path) - if err != nil { - return nil, err - } + for _, b := range payloads { transforms, err := conf.ReadTransforms(b) if err != nil { - return nil, fmt.Errorf("parse %s: %w", path, err) + return nil, err } layers = append(layers, transforms) } - return conf.MergeTransforms(layers), nil } // ReadProps discovers and merges props.conf files from splunkHome using // standard Splunk precedence. Returns nil (no error) when absent. func ReadProps(splunkHome string) ([]conf.Prop, error) { - etcApps := filepath.Join(splunkHome, "etc", "apps") - - appDefaults, _ := filepath.Glob(filepath.Join(etcApps, "*", "default", "props.conf")) - sort.Strings(appDefaults) - appLocals, _ := filepath.Glob(filepath.Join(etcApps, "*", "local", "props.conf")) - sort.Strings(appLocals) - + payloads, err := readConfFiles(confFilePaths(splunkHome, "props.conf")) + if err != nil { + return nil, err + } var layers [][]conf.Prop - for _, path := range append(appDefaults, appLocals...) { - b, err := os.ReadFile(path) - if err != nil { - return nil, err - } + for _, b := range payloads { props, err := conf.ReadProps(b) if err != nil { - return nil, fmt.Errorf("parse %s: %w", path, err) + return nil, err } layers = append(layers, props) } - return conf.MergeProps(layers), nil } // ReadOutputs discovers and merges outputs.conf files from splunkHome using -// standard Splunk precedence (lowest to highest): -// -// 1. etc/system/default/outputs.conf -// 2. etc/apps/*/default/outputs.conf (sorted by app name) -// 3. etc/apps/*/local/outputs.conf (sorted by app name) -// 4. etc/system/local/outputs.conf -// -// Returns the merged stanza -> key -> value map. Callers use HTTPOut (or -// future TCPOut, SyslogOut, etc.) to extract a specific output type. -func ReadOutputs(splunkHome string) (map[string]map[string]string, error) { - candidates := outputsConfPaths(splunkHome) - - var layers []map[string]map[string]string - for _, path := range candidates { - b, err := os.ReadFile(path) - if errors.Is(err, os.ErrNotExist) { - continue - } - if err != nil { - return nil, err - } +// standard Splunk precedence. Returns the merged ConfMap. Callers use HTTPOut +// (or future TCPOut, SyslogOut, etc.) to extract a specific output type. +func ReadOutputs(splunkHome string) (conf.ConfMap, error) { + payloads, err := readConfFiles(confFilePaths(splunkHome, "outputs.conf")) + if err != nil { + return nil, err + } + var layers []conf.ConfMap + for _, b := range payloads { parsed, err := conf.ParseConf(b) if err != nil { - return nil, fmt.Errorf("parse %s: %w", path, err) + return nil, err } layers = append(layers, parsed) } - return conf.MergeConf(layers), nil } // HTTPOut extracts the [httpout] stanza from a merged outputs conf map. // Returns conf.ErrNoHTTPOut if no [httpout] stanza is present. -func HTTPOut(merged map[string]map[string]string) (*conf.Output, error) { +func HTTPOut(merged conf.ConfMap) (*conf.Output, error) { return conf.HTTPOut(merged) } -// outputsConfPaths returns the ordered list of outputs.conf paths to consider, -// from lowest to highest precedence. -func outputsConfPaths(splunkHome string) []string { - etcDir := filepath.Join(splunkHome, "etc") - var paths []string - - // 1. system default - paths = append(paths, filepath.Join(etcDir, "system", "default", "outputs.conf")) - - // 2. app defaults (sorted by app name) - appDefaults, _ := filepath.Glob(filepath.Join(etcDir, "apps", "*", "default", "outputs.conf")) - sort.Strings(appDefaults) - paths = append(paths, appDefaults...) - - // 3. app locals (sorted by app name) - appLocals, _ := filepath.Glob(filepath.Join(etcDir, "apps", "*", "local", "outputs.conf")) - sort.Strings(appLocals) - paths = append(paths, appLocals...) - - // 4. system local (highest precedence) - paths = append(paths, filepath.Join(etcDir, "system", "local", "outputs.conf")) - - return paths -} - // CreateExporter builds a logs exporter from a merged outputs conf map. // It extracts [httpout] and builds a HEC exporter. Future output types // (tcpout, syslog, etc.) will be added here as additional cases. -func CreateExporter(merged map[string]map[string]string, logger *zap.Logger, telemetrySettings component.TelemetrySettings) (exporter.Logs, error) { +func CreateExporter(merged conf.ConfMap, logger *zap.Logger, telemetrySettings component.TelemetrySettings) (exporter.Logs, error) { output, err := conf.HTTPOut(merged) if err != nil { return nil, err From 26fd3402c20a798ae82e7526360813b2f2ed42a9 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Tue, 18 Aug 2026 09:15:11 +0200 Subject: [PATCH 04/46] simplify hasScheme function --- internal/conf/inputs.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index bec81a0..bb78123 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -114,11 +114,7 @@ func resolveInputPath(name, appDir string) string { } func hasScheme(name, scheme string) bool { - prefix := scheme + "://" - if len(name) < len(prefix) { - return false - } - return strings.EqualFold(name[:len(prefix)], prefix) + return strings.HasPrefix(strings.ToLower(name), scheme+"://") } func absPath(appDir, rel string) string { From cecd7c144d6b6b57fc33301cf3d1f598f6a5840b Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 19 Aug 2026 15:37:42 +0200 Subject: [PATCH 05/46] draft of splunk_ta_observer --- go.mod | 1 + go.sum | 2 + pkg/splunkinputsreceiver/config.go | 7 +++ pkg/splunkinputsreceiver/go.mod | 41 +++++++++++++++++ pkg/splunkinputsreceiver/go.sum | 67 ++++++++++++++++++++++++++++ pkg/splunkinputsreceiver/receiver.go | 37 +++++++++++---- 6 files changed, 146 insertions(+), 9 deletions(-) diff --git a/go.mod b/go.mod index e42ef59..00da339 100644 --- a/go.mod +++ b/go.mod @@ -67,6 +67,7 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.157.0 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.157.0 // indirect diff --git a/go.sum b/go.sum index e1e31c6..2da8c6b 100644 --- a/go.sum +++ b/go.sum @@ -142,6 +142,8 @@ github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexpo github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0/go.mod h1:EEHR9WkKjnZA30xrn7tz6jiczE49RVJnQOgLqS4nJDE= github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.157.0 h1:biFNL7OCavTRGSRqHuu83uw6yDu4tUPHhze0FN48aOE= github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.157.0/go.mod h1:XcMDJnqBbYCENJloY7w8t5f09SIB1H/R2QOpIUa4U3o= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0 h1:ZUHfd8qumKAMctr0CnRdsMjfke/RBU8702O3auyWMtM= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0/go.mod h1:x6bJlY9KOnHTbRqKhdmR5jbutjunFsDC5r+OP60+RRU= github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.157.0 h1:aQVvm4iiT1DUx2CzeDLEWETDKgbBvWAPJgancj6SQXY= diff --git a/pkg/splunkinputsreceiver/config.go b/pkg/splunkinputsreceiver/config.go index 337fca9..4af86a5 100644 --- a/pkg/splunkinputsreceiver/config.go +++ b/pkg/splunkinputsreceiver/config.go @@ -7,5 +7,12 @@ type Config struct { // BaseDir is the Splunk installation root ($SPLUNK_HOME). inputs.conf, // transforms.conf, and props.conf are discovered across etc/apps/* using // standard Splunk precedence. Overrides $SPLUNK_HOME when set. + // Mutually exclusive with Path. BaseDir string `mapstructure:"base_dir"` + + // Path is the absolute path to a single TA directory. When set, only that + // directory's inputs.conf, transforms.conf, and props.conf are read with no + // btool-style layering. Intended for use with the splunk_ta_observer via + // receiver_creator. Mutually exclusive with BaseDir. + Path string `mapstructure:"path"` } diff --git a/pkg/splunkinputsreceiver/go.mod b/pkg/splunkinputsreceiver/go.mod index 5fc056e..d7107ec 100644 --- a/pkg/splunkinputsreceiver/go.mod +++ b/pkg/splunkinputsreceiver/go.mod @@ -10,21 +10,32 @@ require ( ) require ( + github.com/Microsoft/go-winio v0.6.2 // indirect github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v7 v7.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/elastic/lunes v0.2.2 // indirect github.com/expr-lang/expr v1.17.8 // indirect + github.com/felixge/httpsnoop v1.1.0 // indirect github.com/foxboron/go-tpm-keyfiles v0.0.0-20260427185012-515ba073c4c1 // indirect github.com/fsnotify/fsnotify v1.10.1 // indirect + github.com/go-logr/logr v1.4.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-viper/mapstructure/v2 v2.5.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.6 // indirect + github.com/golang/snappy v1.0.0 // indirect github.com/google/go-tpm v0.9.9-0.20260124013517-8f8f42cba0de // indirect + github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 // indirect github.com/hashicorp/go-version v1.9.0 // indirect + github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect github.com/jonboulle/clockwork v0.5.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.19.1 // indirect + github.com/klauspost/cpuid/v2 v2.4.0 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/providers/confmap v1.0.0 // indirect github.com/knadh/koanf/v2 v2.3.5 // indirect @@ -35,25 +46,53 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.157.0 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.157.0 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchperresourceattr v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.157.0 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof v0.157.0 // indirect + github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk v0.157.0 // indirect + github.com/pierrec/lz4/v4 v4.1.27 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/rs/cors v1.11.1 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/valyala/fastjson v1.6.10 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/collector/client v1.63.0 // indirect + go.opentelemetry.io/collector/config/configauth v1.63.0 // indirect + go.opentelemetry.io/collector/config/configcompression v1.63.0 // indirect + go.opentelemetry.io/collector/config/confighttp v0.157.0 // indirect + go.opentelemetry.io/collector/config/configmiddleware v1.63.0 // indirect + go.opentelemetry.io/collector/config/confignet v1.63.0 // indirect go.opentelemetry.io/collector/config/configopaque v1.63.0 // indirect + go.opentelemetry.io/collector/config/configoptional v1.63.0 // indirect + go.opentelemetry.io/collector/config/configretry v1.63.0 // indirect go.opentelemetry.io/collector/config/configtls v1.63.0 // indirect go.opentelemetry.io/collector/confmap v1.63.0 // indirect go.opentelemetry.io/collector/consumer/consumererror v0.157.0 // indirect + go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.157.0 // indirect go.opentelemetry.io/collector/consumer/consumertest v0.157.0 // indirect go.opentelemetry.io/collector/consumer/xconsumer v0.157.0 // indirect + go.opentelemetry.io/collector/exporter v1.63.0 // indirect + go.opentelemetry.io/collector/exporter/exporterhelper v0.157.0 // indirect + go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.157.0 // indirect + go.opentelemetry.io/collector/exporter/xexporter v0.157.0 // indirect go.opentelemetry.io/collector/extension v1.63.0 // indirect + go.opentelemetry.io/collector/extension/extensionauth v1.63.0 // indirect + go.opentelemetry.io/collector/extension/extensionmiddleware v0.157.0 // indirect go.opentelemetry.io/collector/extension/xextension v0.157.0 // indirect go.opentelemetry.io/collector/featuregate v1.63.0 // indirect go.opentelemetry.io/collector/internal/componentalias v0.157.0 // indirect go.opentelemetry.io/collector/pdata v1.63.0 // indirect go.opentelemetry.io/collector/pdata/pprofile v0.157.0 // indirect + go.opentelemetry.io/collector/pdata/xpdata v0.157.0 // indirect go.opentelemetry.io/collector/pipeline v1.63.0 // indirect go.opentelemetry.io/collector/pipeline/xpipeline v0.157.0 // indirect go.opentelemetry.io/collector/receiver/receiverhelper v0.157.0 // indirect go.opentelemetry.io/collector/receiver/xreceiver v0.157.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect go.opentelemetry.io/otel v1.44.0 // indirect go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect @@ -61,6 +100,7 @@ require ( go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.54.0 // indirect + golang.org/x/net v0.57.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.40.0 // indirect gonum.org/v1/gonum v0.17.0 // indirect @@ -68,6 +108,7 @@ require ( google.golang.org/grpc v1.82.1 // indirect google.golang.org/protobuf v1.36.11 // indirect gopkg.in/ini.v1 v1.67.3 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) replace github.com/splunk/tarunner => ../../ diff --git a/pkg/splunkinputsreceiver/go.sum b/pkg/splunkinputsreceiver/go.sum index 9ff55eb..04508e7 100644 --- a/pkg/splunkinputsreceiver/go.sum +++ b/pkg/splunkinputsreceiver/go.sum @@ -1,7 +1,11 @@ +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v7 v7.0.0 h1:ZP+QAaaOnVUHo+ufFpZ835hbT3x2fy+h2lecVEosZ6A= +github.com/cenkalti/backoff/v7 v7.0.0/go.mod h1:qcKBGwsu4hpxHtQ8tWYsQ+ifzx2+sS+Xx/3jfe30lI8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -11,10 +15,13 @@ github.com/elastic/lunes v0.2.2 h1:dZFEaebNg9l+mzvOQN6Nd/c9y6y8rUe3tBWsTgvM08U= github.com/elastic/lunes v0.2.2/go.mod h1:u3W/BdONWTrh0JjNZ21C907dDc+cUZttZrGa625nf2k= github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= github.com/expr-lang/expr v1.17.8/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= +github.com/felixge/httpsnoop v1.1.0 h1:3YtUj32ZZkqZtt3sZZsClsymw/QDuVfpNhoA31zeORc= +github.com/felixge/httpsnoop v1.1.0/go.mod h1:Zqxgdd+1Rkcz8euOqdr7lqgCRJztwr5hp9vDSi5UZCE= github.com/foxboron/go-tpm-keyfiles v0.0.0-20260427185012-515ba073c4c1 h1:/IDZxzpOhFdoDcVQT9Eaf2kY3grH5AUK+5MqoFq6Yng= github.com/foxboron/go-tpm-keyfiles v0.0.0-20260427185012-515ba073c4c1/go.mod h1:wxFx38LbEL4RF0JH6PR3lf7ZJ6ZO0yQWQstLCTQQNjA= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= @@ -27,6 +34,8 @@ github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= @@ -35,16 +44,24 @@ github.com/google/go-tpm v0.9.9-0.20260124013517-8f8f42cba0de/go.mod h1:h9jEsEEC github.com/google/go-tpm-tools v0.4.7 h1:J3ycC8umYxM9A4eF73EofRZu4BxY0jjQnUnkhIBbvws= github.com/google/go-tpm-tools v0.4.7/go.mod h1:gSyXTZHe3fgbzb6WEGd90QucmsnT1SRdlye82gH8QjQ= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0 h1:du0WGc8xSKq/++e0cglxhS/mXVqsR7+c7jLEi5Vqduw= +github.com/google/pprof v0.0.0-20260709232956-b9395ee17fa0/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= +github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw= +github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU= github.com/knadh/koanf/maps v0.1.2 h1:RBfmAW5CnZT+PJ1CVc1QSJKf4Xu9kxfQgYVQSu8hpbo= github.com/knadh/koanf/maps v0.1.2/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI= github.com/knadh/koanf/providers/confmap v1.0.0 h1:mHKLJTE7iXEys6deO5p6olAiZdG5zwp8Aebir+/EaRE= @@ -71,18 +88,32 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0 h1:65Y/2RuI7ov7PbwL4EE4Jf0J/2GIKGRN+lmmRU7sj7k= +github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0/go.mod h1:EEHR9WkKjnZA30xrn7tz6jiczE49RVJnQOgLqS4nJDE= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0 h1:ZUHfd8qumKAMctr0CnRdsMjfke/RBU8702O3auyWMtM= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0/go.mod h1:x6bJlY9KOnHTbRqKhdmR5jbutjunFsDC5r+OP60+RRU= github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.157.0 h1:aQVvm4iiT1DUx2CzeDLEWETDKgbBvWAPJgancj6SQXY= github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.157.0/go.mod h1:O8CaK9NpZseY5l4LSxsOkH3QQVpIK+qwoAAXUPWrZdg= github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.157.0 h1:VSSYnwXzCdpNOfWkBj0JWOy/iEj+BwS97MEgDquQP6k= github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.157.0/go.mod h1:CJVh4lwbI8NZ7lrYvM5AUNSyMkeiM/iRJqOWzDGjd8s= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.157.0 h1:OWbiwK7Uz8h1CHjpLSU0GRxVHh/RdjtbzVFU/0DG6Z0= +github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.157.0/go.mod h1:YFpi+9Ouk7mRvAcXry+qMetG3N1hxYovGoYCY/sISII= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchperresourceattr v0.157.0 h1:bR+johTWfDnGCYcl6pgz5RO8h5/akatNJKPCItXIU5o= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchperresourceattr v0.157.0/go.mod h1:QizuzD7e2qjZve5jCf3Qjh235cuxfQi4XiYkHxHFTIE= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.157.0 h1:IGzazc7VSPKjMGem50fulBsU6P5esO1t4/7vCo1FAvw= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.157.0/go.mod h1:cQDc//mBJ6FcKYe6LXciiugTpGtte7ODX0XphSY5uHc= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof v0.157.0 h1:JrYk+H65J18Gn6JXf5zjNQkCv4HoU7LRHm6nSz6qOVs= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof v0.157.0/go.mod h1:Xb2YX/ehDKQHwhpwSkTCe0t6IBDv406KTA3eMpPY8Lw= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk v0.157.0 h1:XyPUqYfHHp4bCZ/v3btPskHva3ZAWcQWc6LV0hGHlPk= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk v0.157.0/go.mod h1:u/jArGoFB0a3ITN5MIWrBFvjvacDIUSx37Rc00iW4tI= +github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= +github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= +github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -97,15 +128,33 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= +github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= +github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/collector v0.157.0 h1:+RLtpnbZQSWcPedeSEa02OtNVlHaMqS10tTdc0LW3ew= +go.opentelemetry.io/collector/client v1.63.0 h1:QSu/+nsQUldXh8AQz6ooJ5T62wGx9U0nx32BXJrwHrQ= +go.opentelemetry.io/collector/client v1.63.0/go.mod h1:IDDEIMIX0AcOptjCsGh1lTs2inBK8+BWCnkiCaTsSHY= go.opentelemetry.io/collector/component v1.63.0 h1:l98ZCxfCTt/O6dYB0JVKKtewaFLe/a6N2qQe61Tbf2o= go.opentelemetry.io/collector/component v1.63.0/go.mod h1:yLGMmT7jUiqvuGvkqlfR1CBi0dRkSV67tq22I08ZMPk= go.opentelemetry.io/collector/component/componenttest v0.157.0 h1:kTMapOVJ3YMKf5Lu2j/FdcKipn90KkIrnHkKca8uNfk= go.opentelemetry.io/collector/component/componenttest v0.157.0/go.mod h1:AUzvlwDat8AHaNRDm+dzQ59uaEXQO1qTWccjkRjqq00= +go.opentelemetry.io/collector/config/configauth v1.63.0 h1:ttyEAMW93tOL2G4QzNLClE2JzljkNiekZZHoYEca40Y= +go.opentelemetry.io/collector/config/configauth v1.63.0/go.mod h1:TTUOFF96LH/lSW0NpIubfzPN0IUo3phIqeiOY0y67CQ= +go.opentelemetry.io/collector/config/configcompression v1.63.0 h1:9fkVQZ8Ia/qHd+4vyPxSDqGuDyMI9nIZXlxSFQgd76Q= +go.opentelemetry.io/collector/config/configcompression v1.63.0/go.mod h1:SEcE2uFLHHPc/Vi8WCkW5MhOMUwaT321HBdZ3P8x8D0= +go.opentelemetry.io/collector/config/confighttp v0.157.0 h1:XG0GdWPmPoB/iJdjVAkTlyiE1IdFSudUaetM90OfDZY= +go.opentelemetry.io/collector/config/confighttp v0.157.0/go.mod h1:f6XwATfBREqb71wqpjyGOEDT8NZNu10BknZSj+H0ydQ= +go.opentelemetry.io/collector/config/configmiddleware v1.63.0 h1:znpbS4Cno6PAjq224pH3MNbOGtdNJke8oD1esB7LixU= +go.opentelemetry.io/collector/config/configmiddleware v1.63.0/go.mod h1:m5V4/dk/jP2DfYM6xRwk4YEZBWN2fcArn507zEqGOfk= +go.opentelemetry.io/collector/config/confignet v1.63.0 h1:pQxbzx0ADfkl7lqthTXXsJju/X5ChCxh0xshh6qMABM= +go.opentelemetry.io/collector/config/confignet v1.63.0/go.mod h1:Op+r1B/DtzXgIuKEL7/JkTqtJdL9veu2uEXvSxH3lks= go.opentelemetry.io/collector/config/configopaque v1.63.0 h1:+Xaya8m3G6Eq7E0436a3yxqHmq44kC6b341+5AwxQAI= go.opentelemetry.io/collector/config/configopaque v1.63.0/go.mod h1:GGIOZ5UUlO6+D81QD3DlH4QrjfEUb/wffq351dPtTa4= +go.opentelemetry.io/collector/config/configoptional v1.63.0 h1:f7cITrZl5hd/zYG2RY7nLZYnLja4EOO0nhnEb6iRbhY= +go.opentelemetry.io/collector/config/configoptional v1.63.0/go.mod h1:ALCzSs7PqGeopNJB+Sp1hWEENcSsHCI8i2DRHwrl678= +go.opentelemetry.io/collector/config/configretry v1.63.0 h1:+rp8xKUuHbglqlcn7kQlgtihs+c92Qg6bBGQxmcTvNQ= +go.opentelemetry.io/collector/config/configretry v1.63.0/go.mod h1:W6bJYhzZ3FQ2Tg0K5SWprF3l7MotMqD1uQbgYm00SU8= go.opentelemetry.io/collector/config/configtls v1.63.0 h1:THouqm+PdXd38NTr+nG18y1uuexZ0mrj4QPKyTtzklo= go.opentelemetry.io/collector/config/configtls v1.63.0/go.mod h1:bdqkgRUfNnoBg17DixR5nH+6L84Sdrsop7+mzWlCCdk= go.opentelemetry.io/collector/confmap v1.63.0 h1:1THBabHoQc8t/9r6ztMsghiO1OxDPZpYtn0cuwwsxYI= @@ -114,12 +163,26 @@ go.opentelemetry.io/collector/consumer v1.63.0 h1:0eOZh0qmDg6HCJaiUqI9FAb4BD4fKJ go.opentelemetry.io/collector/consumer v1.63.0/go.mod h1:IVhjv4d+PmSf4Ttz/guJFbWJtRM3Ld3nRcZ12gxy6PA= go.opentelemetry.io/collector/consumer/consumererror v0.157.0 h1:ljztZISi+MgMz5DVPq0BT7tH0OX9MRCKPFXXLcm7zWc= go.opentelemetry.io/collector/consumer/consumererror v0.157.0/go.mod h1:Ggv+XTMUazTbCYkbjJaa+JgVrv6AWFHVjhZ2s6gHQjo= +go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.157.0 h1:28CMI/CNbsYJDhF4i8XUrQwBt7HKXg0A8ZfhCmDA3Kg= +go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.157.0/go.mod h1:GbXNvpCvpjMUL8PLZdU/s2rTgLFV6sbwSuVxlaog6kk= go.opentelemetry.io/collector/consumer/consumertest v0.157.0 h1:zehAVaLn67AWLhhi/LrUY0Tv06XWx5LYCvvmu7xHXHU= go.opentelemetry.io/collector/consumer/consumertest v0.157.0/go.mod h1:HRxCIepTczx1uWnm3VPPNbt0k+N9fG/ENZDF1dGrkQc= go.opentelemetry.io/collector/consumer/xconsumer v0.157.0 h1:DPbDCHDwbwVa0OoikTktkbXAobN7EgC4XbYaNJIyooI= go.opentelemetry.io/collector/consumer/xconsumer v0.157.0/go.mod h1:IepWZ5ZNBUuS5OiCPQaD/Q1buqriiItxaaMYD7nqYdc= +go.opentelemetry.io/collector/exporter v1.63.0 h1:UkLIYaOSmAi1ghov2Ex7rnX9f9h+dyk2h4oAt5+33as= +go.opentelemetry.io/collector/exporter v1.63.0/go.mod h1:txQAKpijiNKFaVjAvR/WiA8vsdRUH/vuWb3a+x1A1X4= +go.opentelemetry.io/collector/exporter/exporterhelper v0.157.0 h1:VWetLY/iJO6DMxc7/mbRiFKkvN17+QS1xk6Qw/0vFgY= +go.opentelemetry.io/collector/exporter/exporterhelper v0.157.0/go.mod h1:ID0lbCndTDkfEnM3WcRi7wxM2kff1IgvKWynj4F3xRs= +go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.157.0 h1:qlN4h4U3iHf635KMV8L/LASoXRwKGFxtVRjCJ6Aff/o= +go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.157.0/go.mod h1:ht3GCeExQVtqcfwjAjFRqyFW18AY5lr+DAJGKJGc7jI= +go.opentelemetry.io/collector/exporter/xexporter v0.157.0 h1:RLORXoJthjdQEnbtmzVYA0W5oFI5ClGLoAEhh+IlqkM= +go.opentelemetry.io/collector/exporter/xexporter v0.157.0/go.mod h1:/Fy0KezweWrZbSOAix3RYvTJzTQ9GrFXfXQGMRDzegk= go.opentelemetry.io/collector/extension v1.63.0 h1:wtZXWEFo4pM5Nw/ZUTiC1JbhG0rfbX4n+4nObN1RD9Y= go.opentelemetry.io/collector/extension v1.63.0/go.mod h1:K4UQiO/T+B3ex5D5UL0H0Jd7xB3NL12qUHisGiCitbU= +go.opentelemetry.io/collector/extension/extensionauth v1.63.0 h1:I2nUDa+r0AVO7cCDvqlKX0JI21LzdYq2SLp1hLxQ0Q8= +go.opentelemetry.io/collector/extension/extensionauth v1.63.0/go.mod h1:bJV7oxY/JWRDXrZDbjuv9DjU0NNNs6r+YQcYkWVzf7o= +go.opentelemetry.io/collector/extension/extensionmiddleware v0.157.0 h1:uC8E0dzheaRCi1Y8WwmtPmElBfS2FfaCkjOQh4sq92Y= +go.opentelemetry.io/collector/extension/extensionmiddleware v0.157.0/go.mod h1:wucOUbf33iZEtOSLtUi7UsULqmlIeMsCp0kIRtlevdw= go.opentelemetry.io/collector/extension/xextension v0.157.0 h1:tRWFvVGMUfPEfog4y0t2mbXz5dxc4rmp/K8J1rAAKiA= go.opentelemetry.io/collector/extension/xextension v0.157.0/go.mod h1:Gegyl4sliQC5IqZNvrO4+HBtkuprxcd17GzGPcLJX5g= go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY= @@ -134,6 +197,8 @@ go.opentelemetry.io/collector/pdata/pprofile v0.157.0 h1:YRTPhwWzdG0pfJmb8p/qpQm go.opentelemetry.io/collector/pdata/pprofile v0.157.0/go.mod h1:kwy/ufNUBkw8PsFPQnAqCvD12OpGU8h9A1cz5S7xS6g= go.opentelemetry.io/collector/pdata/testdata v0.157.0 h1:TEee4jvYe8b0nPPKQnOnLz3LaA/XRtv3ja8J56DWIZc= go.opentelemetry.io/collector/pdata/testdata v0.157.0/go.mod h1:ht50rAkY2bonM0eL+IvGDcAKWPNKdFE+8LjdaFjcOAA= +go.opentelemetry.io/collector/pdata/xpdata v0.157.0 h1:xNZQZmQTPLEHCvGjK0QuDG9q02T8kglabWS1UJxgc/M= +go.opentelemetry.io/collector/pdata/xpdata v0.157.0/go.mod h1:BkbLhFiCx0q7WoNWol1qHec3B5FMm53bPtzGZVwVhXI= go.opentelemetry.io/collector/pipeline v1.63.0 h1:D97Nb1Jsgp8Ks+74jXs24kBZVd4pPdhuKe9dVogjcEo= go.opentelemetry.io/collector/pipeline v1.63.0/go.mod h1:RD90NG3Jbk965Xaqym3JyHkuol4uZJjQVUkD9ddXJIs= go.opentelemetry.io/collector/pipeline/xpipeline v0.157.0 h1:Z+HLVDAnp/pJFcPwfR8FQencUjMHt7FrAnmcjPl2A4U= @@ -146,6 +211,8 @@ go.opentelemetry.io/collector/receiver/receivertest v0.157.0 h1:/ggPcx7fVmBjH/iG go.opentelemetry.io/collector/receiver/receivertest v0.157.0/go.mod h1:hAi8M/Rjj0TyL5Q7LutQVPGSu5D5cHJyXjWbJ/78zCc= go.opentelemetry.io/collector/receiver/xreceiver v0.157.0 h1:mjqUE5EdVpIjG1vShhdsZ++/22fs5gpzLpbnbQfOmbY= go.opentelemetry.io/collector/receiver/xreceiver v0.157.0/go.mod h1:zmpPp/75gM0ixRz9yTktggXMGsuiLPfD+z3Bd3tyIWY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 h1:8tvICD4vSTOOsNrsI4Ljf6C+6UKvpTEH5XY3JMoyPoo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index 920a702..61c71aa 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -58,31 +58,50 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { func createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - splunkHome := cfg.BaseDir - if splunkHome == "" { - splunkHome = os.Getenv("SPLUNK_HOME") + + if cfg.Path != "" && cfg.BaseDir != "" { + return nil, fmt.Errorf("splunk_inputs: path and base_dir are mutually exclusive") } - if splunkHome == "" { - return nil, fmt.Errorf("splunk_inputs: base_dir is not set and SPLUNK_HOME is not defined") + + taDir, err := resolveTADir(cfg) + if err != nil { + return nil, err } - inputs, err := tabuilder.ReadInputs(splunkHome) + inputs, err := tabuilder.ReadInputs(taDir) if err != nil { return nil, err } - transforms, err := tabuilder.ReadTransforms(splunkHome) + transforms, err := tabuilder.ReadTransforms(taDir) if err != nil { return nil, err } - props, err := tabuilder.ReadProps(splunkHome) + props, err := tabuilder.ReadProps(taDir) if err != nil { return nil, err } - receivers, err := tabuilder.CreateReceivers(ctx, inputs, transforms, props, splunkHome, logs, settings.TelemetrySettings) + receivers, err := tabuilder.CreateReceivers(ctx, inputs, transforms, props, taDir, logs, settings.TelemetrySettings) if err != nil { return nil, err } return packReceivers(receivers), nil } + +// resolveTADir returns the directory to read conf files from. +// When Path is set, it is used directly (single-TA mode via receiver_creator). +// Otherwise base_dir or $SPLUNK_HOME is used for the full btool-style walk. +func resolveTADir(cfg Config) (string, error) { + if cfg.Path != "" { + return cfg.Path, nil + } + splunkHome := cfg.BaseDir + if splunkHome == "" { + splunkHome = os.Getenv("SPLUNK_HOME") + } + if splunkHome == "" { + return "", fmt.Errorf("splunk_inputs: path is not set, base_dir is not set, and SPLUNK_HOME is not defined") + } + return splunkHome, nil +} From 097c781327974068e40589cd5477bca722345d86 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 19 Aug 2026 16:05:40 +0200 Subject: [PATCH 06/46] draft of splunk_ta_observer --- pkg/splunktaobserver/config.go | 17 ++++++ pkg/splunktaobserver/doc.go | 4 ++ pkg/splunktaobserver/extension.go | 86 +++++++++++++++++++++++++++++++ pkg/splunktaobserver/factory.go | 33 ++++++++++++ pkg/splunktaobserver/go.mod | 25 +++++++++ pkg/splunktaobserver/go.sum | 70 +++++++++++++++++++++++++ 6 files changed, 235 insertions(+) create mode 100644 pkg/splunktaobserver/config.go create mode 100644 pkg/splunktaobserver/doc.go create mode 100644 pkg/splunktaobserver/extension.go create mode 100644 pkg/splunktaobserver/factory.go create mode 100644 pkg/splunktaobserver/go.mod create mode 100644 pkg/splunktaobserver/go.sum diff --git a/pkg/splunktaobserver/config.go b/pkg/splunktaobserver/config.go new file mode 100644 index 0000000..9ae31af --- /dev/null +++ b/pkg/splunktaobserver/config.go @@ -0,0 +1,17 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package splunktaobserver + +import "time" + +// Config defines configuration for the Splunk TA observer. +type Config struct { + // Path is the directory to scan for TA subdirectories, typically + // $SPLUNK_HOME/etc/apps. + Path string `mapstructure:"path"` + + // RefreshInterval controls how often the directory is re-scanned for + // added or removed TAs. + RefreshInterval time.Duration `mapstructure:"refresh_interval"` +} diff --git a/pkg/splunktaobserver/doc.go b/pkg/splunktaobserver/doc.go new file mode 100644 index 0000000..137b77d --- /dev/null +++ b/pkg/splunktaobserver/doc.go @@ -0,0 +1,4 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package splunktaobserver diff --git a/pkg/splunktaobserver/extension.go b/pkg/splunktaobserver/extension.go new file mode 100644 index 0000000..a661b91 --- /dev/null +++ b/pkg/splunktaobserver/extension.go @@ -0,0 +1,86 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package splunktaobserver + +import ( + "context" + "os" + "path/filepath" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/endpointswatcher" +) + +// TADirectoryType identifies an endpoint as a Splunk TA directory. +const TADirectoryType observer.EndpointType = "splunk.ta_directory" + +// TADirectory holds details about a discovered TA directory endpoint. +type TADirectory struct { + // Name is the TA folder name, e.g. "Splunk_TA_nix". + Name string + // Path is the absolute path to the TA directory. + Path string +} + +func (t *TADirectory) Type() observer.EndpointType { return TADirectoryType } +func (t *TADirectory) Env() observer.EndpointEnv { + return observer.EndpointEnv{ + "name": t.Name, + "path": t.Path, + } +} + +type taObserver struct { + *endpointswatcher.EndpointsWatcher +} + +var _ extension.Extension = (*taObserver)(nil) + +func newObserver(params extension.Settings, config *Config) (extension.Extension, error) { + return &taObserver{ + EndpointsWatcher: endpointswatcher.New( + &taLister{path: config.Path, logger: params.Logger}, + config.RefreshInterval, + params.Logger, + ), + }, nil +} + +func (*taObserver) Start(context.Context, component.Host) error { return nil } + +func (o *taObserver) Shutdown(context.Context) error { + o.StopListAndWatch() + return nil +} + +type taLister struct { + path string + logger *zap.Logger +} + +func (l *taLister) ListEndpoints() []observer.Endpoint { + entries, err := os.ReadDir(l.path) + if err != nil { + l.logger.Error("failed to read TA directory", zap.String("path", l.path), zap.Error(err)) + return nil + } + + var endpoints []observer.Endpoint + for _, entry := range entries { + if !entry.IsDir() { + continue + } + absPath := filepath.Join(l.path, entry.Name()) + endpoints = append(endpoints, observer.Endpoint{ + ID: observer.EndpointID(absPath), + Target: absPath, + Details: &TADirectory{Name: entry.Name(), Path: absPath}, + }) + } + return endpoints +} diff --git a/pkg/splunktaobserver/factory.go b/pkg/splunktaobserver/factory.go new file mode 100644 index 0000000..69f9b62 --- /dev/null +++ b/pkg/splunktaobserver/factory.go @@ -0,0 +1,33 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package splunktaobserver + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" +) + +const defaultRefreshInterval = 60 * time.Second + +func NewFactory() extension.Factory { + return extension.NewFactory( + component.MustNewType("splunk_ta_observer"), + createDefaultConfig, + createExtension, + component.StabilityLevelDevelopment, + ) +} + +func createDefaultConfig() component.Config { + return &Config{ + RefreshInterval: defaultRefreshInterval, + } +} + +func createExtension(_ context.Context, params extension.Settings, cfg component.Config) (extension.Extension, error) { + return newObserver(params, cfg.(*Config)) +} diff --git a/pkg/splunktaobserver/go.mod b/pkg/splunktaobserver/go.mod new file mode 100644 index 0000000..0c9986d --- /dev/null +++ b/pkg/splunktaobserver/go.mod @@ -0,0 +1,25 @@ +module github.com/splunk/tarunner/pkg/splunktaobserver + +go 1.26.4 + +require ( + github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 + go.opentelemetry.io/collector/component v1.63.0 + go.opentelemetry.io/collector/extension v1.63.0 + go.uber.org/zap v1.28.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + go.opentelemetry.io/collector/featuregate v1.63.0 // indirect + go.opentelemetry.io/collector/internal/componentalias v0.157.0 // indirect + go.opentelemetry.io/collector/pdata v1.63.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.uber.org/multierr v1.11.0 // indirect +) diff --git a/pkg/splunktaobserver/go.sum b/pkg/splunktaobserver/go.sum new file mode 100644 index 0000000..833af67 --- /dev/null +++ b/pkg/splunktaobserver/go.sum @@ -0,0 +1,70 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/collector/component v1.63.0 h1:l98ZCxfCTt/O6dYB0JVKKtewaFLe/a6N2qQe61Tbf2o= +go.opentelemetry.io/collector/component v1.63.0/go.mod h1:yLGMmT7jUiqvuGvkqlfR1CBi0dRkSV67tq22I08ZMPk= +go.opentelemetry.io/collector/extension v1.63.0 h1:wtZXWEFo4pM5Nw/ZUTiC1JbhG0rfbX4n+4nObN1RD9Y= +go.opentelemetry.io/collector/extension v1.63.0/go.mod h1:K4UQiO/T+B3ex5D5UL0H0Jd7xB3NL12qUHisGiCitbU= +go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY= +go.opentelemetry.io/collector/featuregate v1.63.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/internal/componentalias v0.157.0 h1:bvVANHVkXRhoRBc92rDsbiC0OHlu9/EbRqPROD9qrPc= +go.opentelemetry.io/collector/internal/componentalias v0.157.0/go.mod h1:PCLANRXGlMhf9NmU+JPFHtZuY4WpMOsccXlDHTDTu1w= +go.opentelemetry.io/collector/internal/testutil v0.157.0 h1:plojUQwFC5l1ex9KUDaLmCFY/mTxEmf3zrlP7M23IEw= +go.opentelemetry.io/collector/internal/testutil v0.157.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= +go.opentelemetry.io/collector/pdata v1.63.0 h1:fY2xSG2MnyoBwA4GUhzoogGZMuNS0qHpCoODqaKwiVQ= +go.opentelemetry.io/collector/pdata v1.63.0/go.mod h1:jzozYYhQEkTQ/CCbCBNC+hYUeju9S2J8HIqIDHdxZWk= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= +go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From cee0ee3fcef00410f281f4c34324fc4af8a847c9 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:36:37 +0200 Subject: [PATCH 07/46] add watch observer under splunk inputs --- pkg/splunkinputsreceiver/config.go | 13 ++- pkg/splunkinputsreceiver/go.mod | 3 +- pkg/splunkinputsreceiver/go.sum | 96 +++++++++++++++++++- pkg/splunkinputsreceiver/receiver.go | 131 ++++++++++++++++++++++++++- 4 files changed, 232 insertions(+), 11 deletions(-) diff --git a/pkg/splunkinputsreceiver/config.go b/pkg/splunkinputsreceiver/config.go index 4af86a5..5edf5ff 100644 --- a/pkg/splunkinputsreceiver/config.go +++ b/pkg/splunkinputsreceiver/config.go @@ -3,16 +3,23 @@ package splunkinputsreceiver +import "go.opentelemetry.io/collector/component" + type Config struct { // BaseDir is the Splunk installation root ($SPLUNK_HOME). inputs.conf, // transforms.conf, and props.conf are discovered across etc/apps/* using // standard Splunk precedence. Overrides $SPLUNK_HOME when set. - // Mutually exclusive with Path. + // Mutually exclusive with Path and WatchObservers. BaseDir string `mapstructure:"base_dir"` // Path is the absolute path to a single TA directory. When set, only that // directory's inputs.conf, transforms.conf, and props.conf are read with no - // btool-style layering. Intended for use with the splunk_ta_observer via - // receiver_creator. Mutually exclusive with BaseDir. + // btool-style layering. Mutually exclusive with BaseDir and WatchObservers. Path string `mapstructure:"path"` + + // WatchObservers is a list of observer extension IDs (e.g. folder_observer) + // to subscribe to. For each directory endpoint received, a splunk_inputs + // sub-receiver is started using that directory as Path. Mutually exclusive + // with BaseDir and Path. + WatchObservers []component.ID `mapstructure:"watch_observers"` } diff --git a/pkg/splunkinputsreceiver/go.mod b/pkg/splunkinputsreceiver/go.mod index d7107ec..f81d90f 100644 --- a/pkg/splunkinputsreceiver/go.mod +++ b/pkg/splunkinputsreceiver/go.mod @@ -3,10 +3,12 @@ module github.com/splunk/tarunner/pkg/splunkinputsreceiver go 1.26.4 require ( + github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 github.com/splunk/tarunner v0.5.1 go.opentelemetry.io/collector/component v1.63.0 go.opentelemetry.io/collector/consumer v1.63.0 go.opentelemetry.io/collector/receiver v1.63.0 + go.uber.org/zap v1.28.0 ) require ( @@ -97,7 +99,6 @@ require ( go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/net v0.57.0 // indirect diff --git a/pkg/splunkinputsreceiver/go.sum b/pkg/splunkinputsreceiver/go.sum index 04508e7..f36f098 100644 --- a/pkg/splunkinputsreceiver/go.sum +++ b/pkg/splunkinputsreceiver/go.sum @@ -1,16 +1,40 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cenkalti/backoff/v7 v7.0.0 h1:ZP+QAaaOnVUHo+ufFpZ835hbT3x2fy+h2lecVEosZ6A= github.com/cenkalti/backoff/v7 v7.0.0/go.mod h1:qcKBGwsu4hpxHtQ8tWYsQ+ifzx2+sS+Xx/3jfe30lI8= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= +github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= +github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= +github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU= +github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/elastic/lunes v0.2.2 h1:dZFEaebNg9l+mzvOQN6Nd/c9y6y8rUe3tBWsTgvM08U= github.com/elastic/lunes v0.2.2/go.mod h1:u3W/BdONWTrh0JjNZ21C907dDc+cUZttZrGa625nf2k= github.com/expr-lang/expr v1.17.8 h1:W1loDTT+0PQf5YteHSTpju2qfUfNoBt4yw9+wOEU9VM= @@ -26,6 +50,8 @@ github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro= github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= @@ -76,12 +102,34 @@ github.com/leodido/go-syslog/v4 v4.6.0 h1:3o9H6K0cj9ITrNt7MG6CJ/Ae+vXmZcEPhI/N30 github.com/leodido/go-syslog/v4 v4.6.0/go.mod h1:BOEXCJSgy32THF4eZWwtZ11w6LrrFVBj+nMtv06ge4w= github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b h1:11UHH39z1RhZ5dc4y4r/4koJo6IYFgTRMe/LlwRTEw0= github.com/leodido/ragel-machinery v0.0.0-20190525184631-5f46317e436b/go.mod h1:WZxr2/6a/Ar9bMDc2rN/LJrE/hF6bXE4LPyDSIxwAfg= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magefile/mage v1.17.2 h1:fyXVu1eadI8Ap1HCCNgEhJ5McIWiYhLR8uol64ZZc40= github.com/magefile/mage v1.17.2/go.mod h1:Yj51kqllmsgFpvvSzgrZPK9WtluG3kUhFaBUVLo4feA= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8= +github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU= +github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg= +github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs= +github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw= +github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -90,6 +138,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0 h1:65Y/2RuI7ov7PbwL4EE4Jf0J/2GIKGRN+lmmRU7sj7k= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0/go.mod h1:EEHR9WkKjnZA30xrn7tz6jiczE49RVJnQOgLqS4nJDE= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0 h1:ZUHfd8qumKAMctr0CnRdsMjfke/RBU8702O3auyWMtM= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0/go.mod h1:x6bJlY9KOnHTbRqKhdmR5jbutjunFsDC5r+OP60+RRU= github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.157.0 h1:aQVvm4iiT1DUx2CzeDLEWETDKgbBvWAPJgancj6SQXY= @@ -100,20 +150,38 @@ github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.157 github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.157.0/go.mod h1:YFpi+9Ouk7mRvAcXry+qMetG3N1hxYovGoYCY/sISII= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchperresourceattr v0.157.0 h1:bR+johTWfDnGCYcl6pgz5RO8h5/akatNJKPCItXIU5o= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/batchperresourceattr v0.157.0/go.mod h1:QizuzD7e2qjZve5jCf3Qjh235cuxfQi4XiYkHxHFTIE= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.157.0 h1:IeELXc6t6ckP+/rNK6Ni0DIB0qY9Hn2RjqdtvtLkgWY= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.157.0/go.mod h1:9wwPZDYZ+mVPTnI3jomH3mQildfH6SBNoqFwlLdWLiw= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.157.0 h1:yM+TsAD0OEr1RdxZ/NB8C0kArZCkgzOjzOp9TbmdX0w= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.157.0/go.mod h1:rugNqTrH2rSgtTV4Sko7rI4WgETpYXwx+Cpi9FfpPuY= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.157.0 h1:oQWc2BKFkPqo14Zh+qDEWZItxJacKSubLjtFirojFnk= +github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.157.0/go.mod h1:fMeXcxEg6tHlCPRjf2jQ6KavnvDrwoGvJ8q8ihDk3BE= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.157.0 h1:IGzazc7VSPKjMGem50fulBsU6P5esO1t4/7vCo1FAvw= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/stanza v0.157.0/go.mod h1:cQDc//mBJ6FcKYe6LXciiugTpGtte7ODX0XphSY5uHc= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof v0.157.0 h1:JrYk+H65J18Gn6JXf5zjNQkCv4HoU7LRHm6nSz6qOVs= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof v0.157.0/go.mod h1:Xb2YX/ehDKQHwhpwSkTCe0t6IBDv406KTA3eMpPY8Lw= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk v0.157.0 h1:XyPUqYfHHp4bCZ/v3btPskHva3ZAWcQWc6LV0hGHlPk= github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk v0.157.0/go.mod h1:u/jArGoFB0a3ITN5MIWrBFvjvacDIUSx37Rc00iW4tI= +github.com/open-telemetry/sig-profiling/profcheck v0.0.0-20260605055552-091960d5da90 h1:Pvu/hHr3XjXFlWQlNk/+dG69b0R1ScGWk8Bgm29lP2g= +github.com/open-telemetry/sig-profiling/profcheck v0.0.0-20260605055552-091960d5da90/go.mod h1:WPdgk1BinVdvYdkbt1KIkgDw6qUiK8CnGq+ftaJ41Ns= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= github.com/pierrec/lz4/v4 v4.1.27 h1:+PhzhWDrjRj89TH2sw43nE3+4+W8lSxIuQadEHZyjUk= github.com/pierrec/lz4/v4 v4.1.27/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA= github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU= +github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM= +github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -126,8 +194,18 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/testcontainers/testcontainers-go v0.43.0 h1:oEQx5MW2DGd9z3AeEQfB2lPM0eLs7ztyaGRu75bFo5A= +github.com/testcontainers/testcontainers-go v0.43.0/go.mod h1:+VxkT2NQnKOZPKi6praMuMKYHYyOGXr0XSBSlSMCzFo= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/valyala/fastjson v1.6.10 h1:/yjJg8jaVQdYR3arGxPE2X5z89xrlhS0eGXdv+ADTh4= github.com/valyala/fastjson v1.6.10/go.mod h1:e6FubmQouUNP73jtMLmcbxS6ydWIpOfhz34TSfO3JaE= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= +github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= @@ -159,6 +237,8 @@ go.opentelemetry.io/collector/config/configtls v1.63.0 h1:THouqm+PdXd38NTr+nG18y go.opentelemetry.io/collector/config/configtls v1.63.0/go.mod h1:bdqkgRUfNnoBg17DixR5nH+6L84Sdrsop7+mzWlCCdk= go.opentelemetry.io/collector/confmap v1.63.0 h1:1THBabHoQc8t/9r6ztMsghiO1OxDPZpYtn0cuwwsxYI= go.opentelemetry.io/collector/confmap v1.63.0/go.mod h1:ksJNAmLTiMkBjMYwXFW1MRRfXYnRsHXA0fW+ZGwb/1U= +go.opentelemetry.io/collector/confmap/xconfmap v0.157.0 h1:jZV8p2wMQhq0ktl1/LAFGOXMCzzfMq56r80r41kKoEs= +go.opentelemetry.io/collector/confmap/xconfmap v0.157.0/go.mod h1:W1wZk6YJ3+IyvIZEmkXH/ejwkUtBV2zCThY6ewZYd6E= go.opentelemetry.io/collector/consumer v1.63.0 h1:0eOZh0qmDg6HCJaiUqI9FAb4BD4fKJNNO+ygMV/WW0w= go.opentelemetry.io/collector/consumer v1.63.0/go.mod h1:IVhjv4d+PmSf4Ttz/guJFbWJtRM3Ld3nRcZ12gxy6PA= go.opentelemetry.io/collector/consumer/consumererror v0.157.0 h1:ljztZISi+MgMz5DVPq0BT7tH0OX9MRCKPFXXLcm7zWc= @@ -175,14 +255,22 @@ go.opentelemetry.io/collector/exporter/exporterhelper v0.157.0 h1:VWetLY/iJO6DMx go.opentelemetry.io/collector/exporter/exporterhelper v0.157.0/go.mod h1:ID0lbCndTDkfEnM3WcRi7wxM2kff1IgvKWynj4F3xRs= go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.157.0 h1:qlN4h4U3iHf635KMV8L/LASoXRwKGFxtVRjCJ6Aff/o= go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.157.0/go.mod h1:ht3GCeExQVtqcfwjAjFRqyFW18AY5lr+DAJGKJGc7jI= +go.opentelemetry.io/collector/exporter/exportertest v0.157.0 h1:WvDEjR6Bv8H6ultrO43tthRH9eRzleDAcPZpqoW64aM= +go.opentelemetry.io/collector/exporter/exportertest v0.157.0/go.mod h1:6TGo5HXuu05R1QCEJRblXry5bIYZb535YYovHHJtNBM= go.opentelemetry.io/collector/exporter/xexporter v0.157.0 h1:RLORXoJthjdQEnbtmzVYA0W5oFI5ClGLoAEhh+IlqkM= go.opentelemetry.io/collector/exporter/xexporter v0.157.0/go.mod h1:/Fy0KezweWrZbSOAix3RYvTJzTQ9GrFXfXQGMRDzegk= go.opentelemetry.io/collector/extension v1.63.0 h1:wtZXWEFo4pM5Nw/ZUTiC1JbhG0rfbX4n+4nObN1RD9Y= go.opentelemetry.io/collector/extension v1.63.0/go.mod h1:K4UQiO/T+B3ex5D5UL0H0Jd7xB3NL12qUHisGiCitbU= go.opentelemetry.io/collector/extension/extensionauth v1.63.0 h1:I2nUDa+r0AVO7cCDvqlKX0JI21LzdYq2SLp1hLxQ0Q8= go.opentelemetry.io/collector/extension/extensionauth v1.63.0/go.mod h1:bJV7oxY/JWRDXrZDbjuv9DjU0NNNs6r+YQcYkWVzf7o= +go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.157.0 h1:/vZvMCZ1XwKiz+Z2gzbsWGV7INN0p1e04/n59EZtsCU= +go.opentelemetry.io/collector/extension/extensionauth/extensionauthtest v0.157.0/go.mod h1:O7EbSlnQyvRIe0/VU6Jvz6UPS/loBg7+SA8IVXGIaC4= go.opentelemetry.io/collector/extension/extensionmiddleware v0.157.0 h1:uC8E0dzheaRCi1Y8WwmtPmElBfS2FfaCkjOQh4sq92Y= go.opentelemetry.io/collector/extension/extensionmiddleware v0.157.0/go.mod h1:wucOUbf33iZEtOSLtUi7UsULqmlIeMsCp0kIRtlevdw= +go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.157.0 h1:AoMVKVqaTLAGSMahO/Hvx9ntiJkL4ncqW9qFrCRFNEQ= +go.opentelemetry.io/collector/extension/extensionmiddleware/extensionmiddlewaretest v0.157.0/go.mod h1:cSOlEz+RCjMm8r4GC9LvS6OOKJhs2FZKQJ0pO9SJaCw= +go.opentelemetry.io/collector/extension/extensiontest v0.157.0 h1:t4FCQgrBI4xWRPZDN9/8BrVu8oHs+jd4xeN5Ft4sdzE= +go.opentelemetry.io/collector/extension/extensiontest v0.157.0/go.mod h1:ZkXhFsaDT4gnB+Au+SuXvboJsl4vlU89rCAqif+7fTo= go.opentelemetry.io/collector/extension/xextension v0.157.0 h1:tRWFvVGMUfPEfog4y0t2mbXz5dxc4rmp/K8J1rAAKiA= go.opentelemetry.io/collector/extension/xextension v0.157.0/go.mod h1:Gegyl4sliQC5IqZNvrO4+HBtkuprxcd17GzGPcLJX5g= go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY= @@ -223,6 +311,10 @@ go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRk go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= +go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0 h1:ZQs05qo3Yh4KUHeVH6v89xErwmsvgA/cLX2/w5Ikp+k= +go.opentelemetry.io/proto/otlp/profiles/v1development v0.3.0/go.mod h1:3iiRVKaCfVo0UI1ZaSMm5WbCBbINRqVlD9SUmvyBNrY= go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index 61c71aa..2edbd09 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -8,10 +8,14 @@ import ( "errors" "fmt" "os" + "sync" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" "github.com/splunk/tarunner/internal/tabuilder" ) @@ -59,15 +63,134 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { func createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - if cfg.Path != "" && cfg.BaseDir != "" { - return nil, fmt.Errorf("splunk_inputs: path and base_dir are mutually exclusive") + exclusiveCount := 0 + if cfg.BaseDir != "" { + exclusiveCount++ + } + if cfg.Path != "" { + exclusiveCount++ + } + if len(cfg.WatchObservers) > 0 { + exclusiveCount++ + } + if exclusiveCount > 1 { + return nil, fmt.Errorf("splunk_inputs: base_dir, path, and watch_observers are mutually exclusive") + } + + if len(cfg.WatchObservers) > 0 { + return &watchingReceiver{ + cfg: cfg, + settings: settings, + logs: logs, + active: make(map[observer.EndpointID]receiver.Logs), + }, nil } taDir, err := resolveTADir(cfg) if err != nil { return nil, err } + return buildReceiver(ctx, taDir, logs, settings) +} + +// watchingReceiver subscribes to one or more observers and starts a sub-receiver +// per directory endpoint. +type watchingReceiver struct { + cfg Config + settings receiver.Settings + logs consumer.Logs + + mu sync.Mutex + active map[observer.EndpointID]receiver.Logs + host component.Host + ctx context.Context + cancel context.CancelFunc +} + +var _ observer.Notify = (*watchingReceiver)(nil) +func (w *watchingReceiver) ID() observer.NotifyID { + return observer.NotifyID(w.settings.ID.String()) +} + +func (w *watchingReceiver) Start(ctx context.Context, host component.Host) error { + w.host = host + w.ctx, w.cancel = context.WithCancel(ctx) + + for _, obsID := range w.cfg.WatchObservers { + ext, ok := host.GetExtensions()[obsID] + if !ok { + return fmt.Errorf("splunk_inputs: observer extension %q not found", obsID) + } + obs, ok := ext.(observer.Observable) + if !ok { + return fmt.Errorf("splunk_inputs: extension %q does not implement observer.Observable", obsID) + } + obs.ListAndWatch(w) + } + return nil +} + +func (w *watchingReceiver) Shutdown(ctx context.Context) error { + if w.cancel != nil { + w.cancel() + } + w.mu.Lock() + defer w.mu.Unlock() + var errs []error + for _, r := range w.active { + errs = append(errs, r.Shutdown(ctx)) + } + return errors.Join(errs...) +} + +func (w *watchingReceiver) OnAdd(endpoints []observer.Endpoint) { + for _, e := range endpoints { + path, ok := e.Details.Env()["path"].(string) + if !ok || path == "" { + w.settings.Logger.Warn("splunk_inputs: endpoint missing path attribute", zap.String("id", string(e.ID))) + continue + } + r, err := buildReceiver(w.ctx, path, w.logs, w.settings) + if err != nil { + w.settings.Logger.Error("splunk_inputs: failed to build receiver for endpoint", + zap.String("path", path), zap.Error(err)) + continue + } + if err := r.Start(w.ctx, w.host); err != nil { + w.settings.Logger.Error("splunk_inputs: failed to start receiver for endpoint", + zap.String("path", path), zap.Error(err)) + continue + } + w.mu.Lock() + w.active[e.ID] = r + w.mu.Unlock() + } +} + +func (w *watchingReceiver) OnRemove(endpoints []observer.Endpoint) { + for _, e := range endpoints { + w.mu.Lock() + r, ok := w.active[e.ID] + if ok { + delete(w.active, e.ID) + } + w.mu.Unlock() + if ok { + if err := r.Shutdown(w.ctx); err != nil { + w.settings.Logger.Error("splunk_inputs: failed to shutdown receiver for removed endpoint", + zap.String("id", string(e.ID)), zap.Error(err)) + } + } + } +} + +func (w *watchingReceiver) OnChange(endpoints []observer.Endpoint) { + w.OnRemove(endpoints) + w.OnAdd(endpoints) +} + +func buildReceiver(ctx context.Context, taDir string, logs consumer.Logs, settings receiver.Settings) (receiver.Logs, error) { inputs, err := tabuilder.ReadInputs(taDir) if err != nil { return nil, err @@ -80,17 +203,15 @@ func createLogsFunc(ctx context.Context, settings receiver.Settings, config comp if err != nil { return nil, err } - receivers, err := tabuilder.CreateReceivers(ctx, inputs, transforms, props, taDir, logs, settings.TelemetrySettings) if err != nil { return nil, err } - return packReceivers(receivers), nil } // resolveTADir returns the directory to read conf files from. -// When Path is set, it is used directly (single-TA mode via receiver_creator). +// When Path is set, it is used directly (single-TA mode). // Otherwise base_dir or $SPLUNK_HOME is used for the full btool-style walk. func resolveTADir(cfg Config) (string, error) { if cfg.Path != "" { From da9a3a1dc2f2a67296d9c16b3b2597b11cb8ddf6 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:52:09 +0200 Subject: [PATCH 08/46] commit folderobserver --- pkg/folderobserver/config.go | 17 +++++++ pkg/folderobserver/doc.go | 4 ++ pkg/folderobserver/extension.go | 78 +++++++++++++++++++++++++++++++++ pkg/folderobserver/factory.go | 33 ++++++++++++++ pkg/folderobserver/go.mod | 25 +++++++++++ pkg/folderobserver/go.sum | 70 +++++++++++++++++++++++++++++ 6 files changed, 227 insertions(+) create mode 100644 pkg/folderobserver/config.go create mode 100644 pkg/folderobserver/doc.go create mode 100644 pkg/folderobserver/extension.go create mode 100644 pkg/folderobserver/factory.go create mode 100644 pkg/folderobserver/go.mod create mode 100644 pkg/folderobserver/go.sum diff --git a/pkg/folderobserver/config.go b/pkg/folderobserver/config.go new file mode 100644 index 0000000..f20b3ae --- /dev/null +++ b/pkg/folderobserver/config.go @@ -0,0 +1,17 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package folderobserver + +import "time" + +// Config defines configuration for the folder observer extension. +type Config struct { + // Path is the directory to watch for subdirectories. Each subdirectory + // found will be emitted as a "folder" endpoint. + Path string `mapstructure:"path"` + + // RefreshInterval controls how often the directory is re-scanned for + // added or removed subdirectories. + RefreshInterval time.Duration `mapstructure:"refresh_interval"` +} diff --git a/pkg/folderobserver/doc.go b/pkg/folderobserver/doc.go new file mode 100644 index 0000000..6310564 --- /dev/null +++ b/pkg/folderobserver/doc.go @@ -0,0 +1,4 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package folderobserver diff --git a/pkg/folderobserver/extension.go b/pkg/folderobserver/extension.go new file mode 100644 index 0000000..242acc1 --- /dev/null +++ b/pkg/folderobserver/extension.go @@ -0,0 +1,78 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package folderobserver + +import ( + "context" + "os" + "path/filepath" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/endpointswatcher" +) + +// folderDetails holds details about a discovered directory endpoint. +type folderDetails struct { + Name string + Path string +} + +func (f *folderDetails) Type() observer.EndpointType { return observer.EndpointType("folder") } +func (f *folderDetails) Env() observer.EndpointEnv { + return observer.EndpointEnv{"name": f.Name, "path": f.Path} +} + +type folderObserver struct { + *endpointswatcher.EndpointsWatcher +} + +var _ extension.Extension = (*folderObserver)(nil) + +func newObserver(params extension.Settings, config *Config) (extension.Extension, error) { + return &folderObserver{ + EndpointsWatcher: endpointswatcher.New( + &folderLister{path: config.Path, logger: params.Logger}, + config.RefreshInterval, + params.Logger, + ), + }, nil +} + +func (*folderObserver) Start(context.Context, component.Host) error { return nil } + +func (o *folderObserver) Shutdown(context.Context) error { + o.StopListAndWatch() + return nil +} + +type folderLister struct { + path string + logger *zap.Logger +} + +func (l *folderLister) ListEndpoints() []observer.Endpoint { + entries, err := os.ReadDir(l.path) + if err != nil { + l.logger.Error("folder_observer: failed to read directory", zap.String("path", l.path), zap.Error(err)) + return nil + } + + var endpoints []observer.Endpoint + for _, entry := range entries { + if !entry.IsDir() { + continue + } + absPath := filepath.Join(l.path, entry.Name()) + endpoints = append(endpoints, observer.Endpoint{ + ID: observer.EndpointID(absPath), + Target: absPath, + Details: &folderDetails{Name: entry.Name(), Path: absPath}, + }) + } + return endpoints +} diff --git a/pkg/folderobserver/factory.go b/pkg/folderobserver/factory.go new file mode 100644 index 0000000..56a5c58 --- /dev/null +++ b/pkg/folderobserver/factory.go @@ -0,0 +1,33 @@ +// Copyright Splunk, Inc. +// SPDX-License-Identifier: Apache-2.0 + +package folderobserver + +import ( + "context" + "time" + + "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/extension" +) + +const defaultRefreshInterval = 60 * time.Second + +func NewFactory() extension.Factory { + return extension.NewFactory( + component.MustNewType("folder_observer"), + createDefaultConfig, + createExtension, + component.StabilityLevelDevelopment, + ) +} + +func createDefaultConfig() component.Config { + return &Config{ + RefreshInterval: defaultRefreshInterval, + } +} + +func createExtension(_ context.Context, params extension.Settings, cfg component.Config) (extension.Extension, error) { + return newObserver(params, cfg.(*Config)) +} diff --git a/pkg/folderobserver/go.mod b/pkg/folderobserver/go.mod new file mode 100644 index 0000000..27946d4 --- /dev/null +++ b/pkg/folderobserver/go.mod @@ -0,0 +1,25 @@ +module github.com/splunk/tarunner/pkg/folderobserver + +go 1.26.4 + +require ( + github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 + go.opentelemetry.io/collector/component v1.63.0 + go.opentelemetry.io/collector/extension v1.63.0 + go.uber.org/zap v1.28.0 +) + +require ( + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + go.opentelemetry.io/collector/featuregate v1.63.0 // indirect + go.opentelemetry.io/collector/internal/componentalias v0.157.0 // indirect + go.opentelemetry.io/collector/pdata v1.63.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.uber.org/multierr v1.11.0 // indirect +) diff --git a/pkg/folderobserver/go.sum b/pkg/folderobserver/go.sum new file mode 100644 index 0000000..833af67 --- /dev/null +++ b/pkg/folderobserver/go.sum @@ -0,0 +1,70 @@ +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/collector/component v1.63.0 h1:l98ZCxfCTt/O6dYB0JVKKtewaFLe/a6N2qQe61Tbf2o= +go.opentelemetry.io/collector/component v1.63.0/go.mod h1:yLGMmT7jUiqvuGvkqlfR1CBi0dRkSV67tq22I08ZMPk= +go.opentelemetry.io/collector/extension v1.63.0 h1:wtZXWEFo4pM5Nw/ZUTiC1JbhG0rfbX4n+4nObN1RD9Y= +go.opentelemetry.io/collector/extension v1.63.0/go.mod h1:K4UQiO/T+B3ex5D5UL0H0Jd7xB3NL12qUHisGiCitbU= +go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY= +go.opentelemetry.io/collector/featuregate v1.63.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= +go.opentelemetry.io/collector/internal/componentalias v0.157.0 h1:bvVANHVkXRhoRBc92rDsbiC0OHlu9/EbRqPROD9qrPc= +go.opentelemetry.io/collector/internal/componentalias v0.157.0/go.mod h1:PCLANRXGlMhf9NmU+JPFHtZuY4WpMOsccXlDHTDTu1w= +go.opentelemetry.io/collector/internal/testutil v0.157.0 h1:plojUQwFC5l1ex9KUDaLmCFY/mTxEmf3zrlP7M23IEw= +go.opentelemetry.io/collector/internal/testutil v0.157.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= +go.opentelemetry.io/collector/pdata v1.63.0 h1:fY2xSG2MnyoBwA4GUhzoogGZMuNS0qHpCoODqaKwiVQ= +go.opentelemetry.io/collector/pdata v1.63.0/go.mod h1:jzozYYhQEkTQ/CCbCBNC+hYUeju9S2J8HIqIDHdxZWk= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= +go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= +go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= +go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 8958f8dd56ffe613b9525bb68a8f44f12d698001 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 11:40:27 +0200 Subject: [PATCH 09/46] add logs --- pkg/folderobserver/extension.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/folderobserver/extension.go b/pkg/folderobserver/extension.go index 242acc1..c3b2130 100644 --- a/pkg/folderobserver/extension.go +++ b/pkg/folderobserver/extension.go @@ -68,11 +68,13 @@ func (l *folderLister) ListEndpoints() []observer.Endpoint { continue } absPath := filepath.Join(l.path, entry.Name()) + l.logger.Info("folder_observer: discovered directory", zap.String("path", absPath)) endpoints = append(endpoints, observer.Endpoint{ ID: observer.EndpointID(absPath), Target: absPath, Details: &folderDetails{Name: entry.Name(), Path: absPath}, }) } + l.logger.Info("folder_observer: scan complete", zap.String("path", l.path), zap.Int("count", len(endpoints))) return endpoints } From dafa02158d783685a5bbef4d6a6907854884de92 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:16:09 +0200 Subject: [PATCH 10/46] add tests --- .../etc/apps/disabled/bin/darwin_arm64/foo | 22 +++++++++++++ .../etc/apps/disabled/bin/linux_amd64/foo | 22 +++++++++++++ .../etc/apps/disabled/default/inputs.conf | 5 +++ .../disabled_interval/bin/darwin_arm64/foo | 22 +++++++++++++ .../disabled_interval/bin/linux_amd64/foo | 22 +++++++++++++ .../disabled_interval/default/inputs.conf | 5 +++ .../etc/apps/periodic/bin/darwin_arm64/foo | 13 ++++++++ .../etc/apps/periodic/bin/linux_amd64/foo | 13 ++++++++ .../etc/apps/periodic/default/inputs.conf | 5 +++ .../script_home/etc/apps/script/bin/foo.sh | 13 ++++++++ .../etc/apps/script/default/inputs.conf | 5 +++ .../ta_home/etc/apps/ta/bin/darwin_arm64/foo | 13 ++++++++ .../ta_home/etc/apps/ta/bin/linux_amd64/foo | 13 ++++++++ .../ta_home/etc/apps/ta/default/inputs.conf | 5 +++ .../tcp_home/etc/apps/tcp/default/inputs.conf | 1 + .../udp_home/etc/apps/udp/default/inputs.conf | 1 + internal/tabuilder/tabuilder.go | 31 +++++++------------ .../etc/apps/my_ta/default/transforms.conf | 2 ++ .../etc/apps/my_ta/local/transforms.conf | 2 ++ .../etc/apps/my_ta/default/transforms.conf | 2 ++ .../etc/apps/my_ta/local/transforms.conf | 2 ++ pkg/splunkinputsreceiver/go.mod | 6 ---- 22 files changed, 200 insertions(+), 25 deletions(-) create mode 100755 internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf create mode 100755 internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf create mode 100755 internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf create mode 100755 internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh create mode 100644 internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf create mode 100755 internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf create mode 100644 internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf create mode 100644 internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf diff --git a/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo b/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo b/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf b/internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf new file mode 100644 index 0000000..8d0899e --- /dev/null +++ b/internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +disabled = 1 +interval = 20 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf new file mode 100644 index 0000000..41a5db9 --- /dev/null +++ b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +# Required parameters for the application to function: +interval = -1 # disabled +sourcetype = _foo +index = diff --git a/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo b/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo b/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf b/internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf new file mode 100644 index 0000000..c530bf7 --- /dev/null +++ b/internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +# Required parameters for the application to function: +interval = 1 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh b/internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf b/internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf new file mode 100644 index 0000000..3f6a5a3 --- /dev/null +++ b/internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf @@ -0,0 +1,5 @@ +[script://./bin/foo.sh] +# Required parameters for the application to function: +interval = 0 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo b/internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo b/internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf b/internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf new file mode 100644 index 0000000..fd2b52d --- /dev/null +++ b/internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +# Required parameters for the application to function: +interval = 0 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf b/internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf new file mode 100644 index 0000000..640b781 --- /dev/null +++ b/internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf @@ -0,0 +1 @@ +[tcp://localhost:4000] diff --git a/internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf b/internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf new file mode 100644 index 0000000..2ad4d88 --- /dev/null +++ b/internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf @@ -0,0 +1 @@ +[udp://localhost:4000] diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 2b0409f..77f6d1f 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -170,12 +170,16 @@ func readConfFiles(paths []string) ([][]byte, error) { return payloads, nil } -// ReadInputs reads inputs.conf, preferring local/ over default/. -func ReadInputs(baseDir string) ([]conf.Input, error) { - fileToRead := filepath.Join(baseDir, "local", "inputs.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { - fileToRead = filepath.Join(baseDir, "default", "inputs.conf") - if _, err := os.Stat(fileToRead); errors.Is(err, os.ErrNotExist) { +// ReadInputs discovers and merges inputs.conf files from splunkHome using +// standard Splunk precedence. Returns nil (no error) when absent. +func ReadInputs(splunkHome string) ([]conf.Input, error) { + var layers [][]conf.Input + for _, path := range confFilePaths(splunkHomeDirs(splunkHome), "inputs.conf") { + b, err := os.ReadFile(path) + if errors.Is(err, os.ErrNotExist) { + continue + } + if err != nil { return nil, err } appDir := filepath.Dir(filepath.Dir(path)) // strip /default or /local @@ -191,7 +195,7 @@ func ReadInputs(baseDir string) ([]conf.Input, error) { // ReadTransforms discovers and merges transforms.conf files from splunkHome // using standard Splunk precedence. Returns nil (no error) when absent. func ReadTransforms(splunkHome string) ([]conf.Transform, error) { - payloads, err := readConfFiles(confFilePaths(splunkHome, "transforms.conf")) + payloads, err := readConfFiles(confFilePaths(splunkHomeDirs(splunkHome), "transforms.conf")) if err != nil { return nil, err } @@ -209,7 +213,7 @@ func ReadTransforms(splunkHome string) ([]conf.Transform, error) { // ReadProps discovers and merges props.conf files from splunkHome using // standard Splunk precedence. Returns nil (no error) when absent. func ReadProps(splunkHome string) ([]conf.Prop, error) { - payloads, err := readConfFiles(confFilePaths(splunkHome, "props.conf")) + payloads, err := readConfFiles(confFilePaths(splunkHomeDirs(splunkHome), "props.conf")) if err != nil { return nil, err } @@ -224,17 +228,6 @@ func ReadProps(splunkHome string) ([]conf.Prop, error) { return conf.MergeProps(layers), nil } -// ReadOutputs discovers and merges outputs.conf files from splunkHome using -// standard Splunk precedence. Returns the merged ConfMap. Callers use HTTPOut -// (or future TCPOut, SyslogOut, etc.) to extract a specific output type. -func ReadOutputs(splunkHome string) (conf.ConfMap, error) { - payloads, err := readConfFiles(confFilePaths(splunkHome, "outputs.conf")) - if err != nil { - return nil, err - } - return conf.ReadProps(b) -} - // ReadOutputs merges outputs.conf across $SPLUNK_HOME using standard Splunk // precedence. Use HTTPOut (or future TCPOut, etc.) to extract a specific type. func ReadOutputs(splunkHome string) (conf.ConfMap, error) { diff --git a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf new file mode 100644 index 0000000..eb3772d --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf @@ -0,0 +1,2 @@ +[example_transform] +REGEX=default diff --git a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf new file mode 100644 index 0000000..7813599 --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf @@ -0,0 +1,2 @@ +[example_transform] +REGEX=local \ No newline at end of file diff --git a/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf new file mode 100644 index 0000000..51c987f --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf @@ -0,0 +1,2 @@ +[example_default] +REGEX=default \ No newline at end of file diff --git a/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf new file mode 100644 index 0000000..071044b --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf @@ -0,0 +1,2 @@ +[example_local] +REGEX=local \ No newline at end of file diff --git a/pkg/splunkinputsreceiver/go.mod b/pkg/splunkinputsreceiver/go.mod index 3cbf6dc..2b1fb9e 100644 --- a/pkg/splunkinputsreceiver/go.mod +++ b/pkg/splunkinputsreceiver/go.mod @@ -98,12 +98,6 @@ require ( go.opentelemetry.io/otel/trace v1.44.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect - github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/pprof v0.157.0 // indirect - github.com/open-telemetry/opentelemetry-collector-contrib/pkg/translator/splunk v0.157.0 // indirect - github.com/pierrec/lz4/v4 v4.1.27 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rs/cors v1.11.1 // indirect - github.com/stretchr/testify v1.11.1 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/net v0.57.0 // indirect From bd182569e9c001994698ab9eb225bbdd4a505952 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:26:04 +0200 Subject: [PATCH 11/46] delete stale code --- pkg/splunktaobserver/config.go | 17 ------ pkg/splunktaobserver/doc.go | 4 -- pkg/splunktaobserver/extension.go | 86 ------------------------------- pkg/splunktaobserver/factory.go | 33 ------------ pkg/splunktaobserver/go.mod | 25 --------- pkg/splunktaobserver/go.sum | 70 ------------------------- 6 files changed, 235 deletions(-) delete mode 100644 pkg/splunktaobserver/config.go delete mode 100644 pkg/splunktaobserver/doc.go delete mode 100644 pkg/splunktaobserver/extension.go delete mode 100644 pkg/splunktaobserver/factory.go delete mode 100644 pkg/splunktaobserver/go.mod delete mode 100644 pkg/splunktaobserver/go.sum diff --git a/pkg/splunktaobserver/config.go b/pkg/splunktaobserver/config.go deleted file mode 100644 index 9ae31af..0000000 --- a/pkg/splunktaobserver/config.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package splunktaobserver - -import "time" - -// Config defines configuration for the Splunk TA observer. -type Config struct { - // Path is the directory to scan for TA subdirectories, typically - // $SPLUNK_HOME/etc/apps. - Path string `mapstructure:"path"` - - // RefreshInterval controls how often the directory is re-scanned for - // added or removed TAs. - RefreshInterval time.Duration `mapstructure:"refresh_interval"` -} diff --git a/pkg/splunktaobserver/doc.go b/pkg/splunktaobserver/doc.go deleted file mode 100644 index 137b77d..0000000 --- a/pkg/splunktaobserver/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package splunktaobserver diff --git a/pkg/splunktaobserver/extension.go b/pkg/splunktaobserver/extension.go deleted file mode 100644 index a661b91..0000000 --- a/pkg/splunktaobserver/extension.go +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package splunktaobserver - -import ( - "context" - "os" - "path/filepath" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/extension" - "go.uber.org/zap" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/endpointswatcher" -) - -// TADirectoryType identifies an endpoint as a Splunk TA directory. -const TADirectoryType observer.EndpointType = "splunk.ta_directory" - -// TADirectory holds details about a discovered TA directory endpoint. -type TADirectory struct { - // Name is the TA folder name, e.g. "Splunk_TA_nix". - Name string - // Path is the absolute path to the TA directory. - Path string -} - -func (t *TADirectory) Type() observer.EndpointType { return TADirectoryType } -func (t *TADirectory) Env() observer.EndpointEnv { - return observer.EndpointEnv{ - "name": t.Name, - "path": t.Path, - } -} - -type taObserver struct { - *endpointswatcher.EndpointsWatcher -} - -var _ extension.Extension = (*taObserver)(nil) - -func newObserver(params extension.Settings, config *Config) (extension.Extension, error) { - return &taObserver{ - EndpointsWatcher: endpointswatcher.New( - &taLister{path: config.Path, logger: params.Logger}, - config.RefreshInterval, - params.Logger, - ), - }, nil -} - -func (*taObserver) Start(context.Context, component.Host) error { return nil } - -func (o *taObserver) Shutdown(context.Context) error { - o.StopListAndWatch() - return nil -} - -type taLister struct { - path string - logger *zap.Logger -} - -func (l *taLister) ListEndpoints() []observer.Endpoint { - entries, err := os.ReadDir(l.path) - if err != nil { - l.logger.Error("failed to read TA directory", zap.String("path", l.path), zap.Error(err)) - return nil - } - - var endpoints []observer.Endpoint - for _, entry := range entries { - if !entry.IsDir() { - continue - } - absPath := filepath.Join(l.path, entry.Name()) - endpoints = append(endpoints, observer.Endpoint{ - ID: observer.EndpointID(absPath), - Target: absPath, - Details: &TADirectory{Name: entry.Name(), Path: absPath}, - }) - } - return endpoints -} diff --git a/pkg/splunktaobserver/factory.go b/pkg/splunktaobserver/factory.go deleted file mode 100644 index 69f9b62..0000000 --- a/pkg/splunktaobserver/factory.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package splunktaobserver - -import ( - "context" - "time" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/extension" -) - -const defaultRefreshInterval = 60 * time.Second - -func NewFactory() extension.Factory { - return extension.NewFactory( - component.MustNewType("splunk_ta_observer"), - createDefaultConfig, - createExtension, - component.StabilityLevelDevelopment, - ) -} - -func createDefaultConfig() component.Config { - return &Config{ - RefreshInterval: defaultRefreshInterval, - } -} - -func createExtension(_ context.Context, params extension.Settings, cfg component.Config) (extension.Extension, error) { - return newObserver(params, cfg.(*Config)) -} diff --git a/pkg/splunktaobserver/go.mod b/pkg/splunktaobserver/go.mod deleted file mode 100644 index 0c9986d..0000000 --- a/pkg/splunktaobserver/go.mod +++ /dev/null @@ -1,25 +0,0 @@ -module github.com/splunk/tarunner/pkg/splunktaobserver - -go 1.26.4 - -require ( - github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 - go.opentelemetry.io/collector/component v1.63.0 - go.opentelemetry.io/collector/extension v1.63.0 - go.uber.org/zap v1.28.0 -) - -require ( - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/hashicorp/go-version v1.9.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - go.opentelemetry.io/collector/featuregate v1.63.0 // indirect - go.opentelemetry.io/collector/internal/componentalias v0.157.0 // indirect - go.opentelemetry.io/collector/pdata v1.63.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect - go.uber.org/multierr v1.11.0 // indirect -) diff --git a/pkg/splunktaobserver/go.sum b/pkg/splunktaobserver/go.sum deleted file mode 100644 index 833af67..0000000 --- a/pkg/splunktaobserver/go.sum +++ /dev/null @@ -1,70 +0,0 @@ -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= -github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/collector/component v1.63.0 h1:l98ZCxfCTt/O6dYB0JVKKtewaFLe/a6N2qQe61Tbf2o= -go.opentelemetry.io/collector/component v1.63.0/go.mod h1:yLGMmT7jUiqvuGvkqlfR1CBi0dRkSV67tq22I08ZMPk= -go.opentelemetry.io/collector/extension v1.63.0 h1:wtZXWEFo4pM5Nw/ZUTiC1JbhG0rfbX4n+4nObN1RD9Y= -go.opentelemetry.io/collector/extension v1.63.0/go.mod h1:K4UQiO/T+B3ex5D5UL0H0Jd7xB3NL12qUHisGiCitbU= -go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY= -go.opentelemetry.io/collector/featuregate v1.63.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= -go.opentelemetry.io/collector/internal/componentalias v0.157.0 h1:bvVANHVkXRhoRBc92rDsbiC0OHlu9/EbRqPROD9qrPc= -go.opentelemetry.io/collector/internal/componentalias v0.157.0/go.mod h1:PCLANRXGlMhf9NmU+JPFHtZuY4WpMOsccXlDHTDTu1w= -go.opentelemetry.io/collector/internal/testutil v0.157.0 h1:plojUQwFC5l1ex9KUDaLmCFY/mTxEmf3zrlP7M23IEw= -go.opentelemetry.io/collector/internal/testutil v0.157.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= -go.opentelemetry.io/collector/pdata v1.63.0 h1:fY2xSG2MnyoBwA4GUhzoogGZMuNS0qHpCoODqaKwiVQ= -go.opentelemetry.io/collector/pdata v1.63.0/go.mod h1:jzozYYhQEkTQ/CCbCBNC+hYUeju9S2J8HIqIDHdxZWk= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= -go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= -go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= From 42d130fd41c91a2d4011743ff18c03f43a16f041 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 13:47:40 +0200 Subject: [PATCH 12/46] fix missing import --- pkg/splunkinputsreceiver/go.mod | 3 ++- pkg/splunkinputsreceiver/go.sum | 2 ++ pkg/splunkinputsreceiver/receiver.go | 1 + 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/splunkinputsreceiver/go.mod b/pkg/splunkinputsreceiver/go.mod index 2b1fb9e..081cc37 100644 --- a/pkg/splunkinputsreceiver/go.mod +++ b/pkg/splunkinputsreceiver/go.mod @@ -3,12 +3,14 @@ module github.com/splunk/tarunner/pkg/splunkinputsreceiver go 1.26.4 require ( + github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 github.com/splunk/tarunner v0.5.1 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/collector/component v1.63.0 go.opentelemetry.io/collector/consumer v1.63.0 go.opentelemetry.io/collector/pdata v1.63.0 go.opentelemetry.io/collector/receiver v1.63.0 + go.uber.org/zap v1.28.0 ) require ( @@ -97,7 +99,6 @@ require ( go.opentelemetry.io/otel/metric v1.44.0 // indirect go.opentelemetry.io/otel/trace v1.44.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect golang.org/x/crypto v0.54.0 // indirect golang.org/x/net v0.57.0 // indirect diff --git a/pkg/splunkinputsreceiver/go.sum b/pkg/splunkinputsreceiver/go.sum index 63e632a..f36f098 100644 --- a/pkg/splunkinputsreceiver/go.sum +++ b/pkg/splunkinputsreceiver/go.sum @@ -138,6 +138,8 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0 h1:65Y/2RuI7ov7PbwL4EE4Jf0J/2GIKGRN+lmmRU7sj7k= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0/go.mod h1:EEHR9WkKjnZA30xrn7tz6jiczE49RVJnQOgLqS4nJDE= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= +github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0 h1:ZUHfd8qumKAMctr0CnRdsMjfke/RBU8702O3auyWMtM= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0/go.mod h1:x6bJlY9KOnHTbRqKhdmR5jbutjunFsDC5r+OP60+RRU= github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.157.0 h1:aQVvm4iiT1DUx2CzeDLEWETDKgbBvWAPJgancj6SQXY= diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index 31205bb..2edbd09 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -11,6 +11,7 @@ import ( "sync" "go.opentelemetry.io/collector/component" + "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" "go.uber.org/zap" From f84c499037ecd7fe2dde90f38e6365b081984482 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:14:03 +0200 Subject: [PATCH 13/46] fix problems after merge --- pkg/splunkinputsreceiver/receiver.go | 80 ++++++++----------------- pkg/splunkinputsreceiver/subreceiver.go | 30 ++++++++-- 2 files changed, 50 insertions(+), 60 deletions(-) diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index 2edbd09..b6d4b32 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -60,45 +60,13 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { } } -func createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { - cfg := config.(Config) - - exclusiveCount := 0 - if cfg.BaseDir != "" { - exclusiveCount++ - } - if cfg.Path != "" { - exclusiveCount++ - } - if len(cfg.WatchObservers) > 0 { - exclusiveCount++ - } - if exclusiveCount > 1 { - return nil, fmt.Errorf("splunk_inputs: base_dir, path, and watch_observers are mutually exclusive") - } - - if len(cfg.WatchObservers) > 0 { - return &watchingReceiver{ - cfg: cfg, - settings: settings, - logs: logs, - active: make(map[observer.EndpointID]receiver.Logs), - }, nil - } - - taDir, err := resolveTADir(cfg) - if err != nil { - return nil, err - } - return buildReceiver(ctx, taDir, logs, settings) -} - // watchingReceiver subscribes to one or more observers and starts a sub-receiver // per directory endpoint. type watchingReceiver struct { cfg Config settings receiver.Settings logs consumer.Logs + opts factoryOptions mu sync.Mutex active map[observer.EndpointID]receiver.Logs @@ -118,6 +86,7 @@ func (w *watchingReceiver) Start(ctx context.Context, host component.Host) error w.ctx, w.cancel = context.WithCancel(ctx) for _, obsID := range w.cfg.WatchObservers { + w.settings.Logger.Info("splunk_inputs: subscribing to observer", zap.String("observer", obsID.String())) ext, ok := host.GetExtensions()[obsID] if !ok { return fmt.Errorf("splunk_inputs: observer extension %q not found", obsID) @@ -126,7 +95,9 @@ func (w *watchingReceiver) Start(ctx context.Context, host component.Host) error if !ok { return fmt.Errorf("splunk_inputs: extension %q does not implement observer.Observable", obsID) } + w.settings.Logger.Info("splunk_inputs: calling ListAndWatch", zap.String("observer", obsID.String())) obs.ListAndWatch(w) + w.settings.Logger.Info("splunk_inputs: ListAndWatch returned", zap.String("observer", obsID.String())) } return nil } @@ -151,12 +122,31 @@ func (w *watchingReceiver) OnAdd(endpoints []observer.Endpoint) { w.settings.Logger.Warn("splunk_inputs: endpoint missing path attribute", zap.String("id", string(e.ID))) continue } - r, err := buildReceiver(w.ctx, path, w.logs, w.settings) + inputs, err := tabuilder.ReadInputs(path) + if err != nil { + w.settings.Logger.Error("splunk_inputs: failed to read inputs for endpoint", + zap.String("path", path), zap.Error(err)) + continue + } + transforms, err := tabuilder.ReadTransforms(path) + if err != nil { + w.settings.Logger.Error("splunk_inputs: failed to read transforms for endpoint", + zap.String("path", path), zap.Error(err)) + continue + } + props, err := tabuilder.ReadProps(path) if err != nil { - w.settings.Logger.Error("splunk_inputs: failed to build receiver for endpoint", + w.settings.Logger.Error("splunk_inputs: failed to read props for endpoint", zap.String("path", path), zap.Error(err)) continue } + receivers, err := w.opts.createReceivers(w.ctx, inputs, transforms, props, path, w.logs, w.settings) + if err != nil { + w.settings.Logger.Error("splunk_inputs: failed to create receivers for endpoint", + zap.String("path", path), zap.Error(err)) + continue + } + r := packReceivers(receivers) if err := r.Start(w.ctx, w.host); err != nil { w.settings.Logger.Error("splunk_inputs: failed to start receiver for endpoint", zap.String("path", path), zap.Error(err)) @@ -190,26 +180,6 @@ func (w *watchingReceiver) OnChange(endpoints []observer.Endpoint) { w.OnAdd(endpoints) } -func buildReceiver(ctx context.Context, taDir string, logs consumer.Logs, settings receiver.Settings) (receiver.Logs, error) { - inputs, err := tabuilder.ReadInputs(taDir) - if err != nil { - return nil, err - } - transforms, err := tabuilder.ReadTransforms(taDir) - if err != nil { - return nil, err - } - props, err := tabuilder.ReadProps(taDir) - if err != nil { - return nil, err - } - receivers, err := tabuilder.CreateReceivers(ctx, inputs, transforms, props, taDir, logs, settings.TelemetrySettings) - if err != nil { - return nil, err - } - return packReceivers(receivers), nil -} - // resolveTADir returns the directory to read conf files from. // When Path is set, it is used directly (single-TA mode). // Otherwise base_dir or $SPLUNK_HOME is used for the full btool-style walk. diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index cc60a6d..cf932b9 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -12,6 +12,8 @@ import ( "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" + "github.com/splunk/tarunner/internal/conf" "github.com/splunk/tarunner/internal/stanza" "github.com/splunk/tarunner/internal/tabuilder" @@ -83,21 +85,39 @@ func newFactoryOptions(opts ...Option) factoryOptions { func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - baseDir := cfg.BaseDir - inputs, err := tabuilder.ReadInputs(baseDir) + + if len(cfg.WatchObservers) > 0 { + if cfg.BaseDir != "" || cfg.Path != "" { + return nil, fmt.Errorf("splunk_inputs: watch_observers is mutually exclusive with base_dir and path") + } + return &watchingReceiver{ + cfg: cfg, + settings: settings, + logs: logs, + opts: o, + active: make(map[observer.EndpointID]receiver.Logs), + }, nil + } + + taDir, err := resolveTADir(cfg) + if err != nil { + return nil, err + } + + inputs, err := tabuilder.ReadInputs(taDir) if err != nil { return nil, err } - transforms, err := tabuilder.ReadTransforms(baseDir) + transforms, err := tabuilder.ReadTransforms(taDir) if err != nil { return nil, err } - props, err := tabuilder.ReadProps(baseDir) + props, err := tabuilder.ReadProps(taDir) if err != nil { return nil, err } - receivers, err := o.createReceivers(ctx, inputs, transforms, props, baseDir, logs, settings) + receivers, err := o.createReceivers(ctx, inputs, transforms, props, taDir, logs, settings) if err != nil { return nil, err } From 32f51f62ab88b4a9c5bc2b612755a1318f58eda2 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 14:42:58 +0200 Subject: [PATCH 14/46] add logging --- pkg/splunkinputsreceiver/receiver.go | 4 ++++ pkg/splunkinputsreceiver/subreceiver.go | 10 +++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index b6d4b32..eeef5e4 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -122,12 +122,14 @@ func (w *watchingReceiver) OnAdd(endpoints []observer.Endpoint) { w.settings.Logger.Warn("splunk_inputs: endpoint missing path attribute", zap.String("id", string(e.ID))) continue } + w.settings.Logger.Info("splunk_inputs: building receivers for endpoint", zap.String("path", path)) inputs, err := tabuilder.ReadInputs(path) if err != nil { w.settings.Logger.Error("splunk_inputs: failed to read inputs for endpoint", zap.String("path", path), zap.Error(err)) continue } + w.settings.Logger.Info("splunk_inputs: read inputs", zap.String("path", path), zap.Int("count", len(inputs))) transforms, err := tabuilder.ReadTransforms(path) if err != nil { w.settings.Logger.Error("splunk_inputs: failed to read transforms for endpoint", @@ -146,12 +148,14 @@ func (w *watchingReceiver) OnAdd(endpoints []observer.Endpoint) { zap.String("path", path), zap.Error(err)) continue } + w.settings.Logger.Info("splunk_inputs: created receivers for endpoint", zap.String("path", path), zap.Int("count", len(receivers))) r := packReceivers(receivers) if err := r.Start(w.ctx, w.host); err != nil { w.settings.Logger.Error("splunk_inputs: failed to start receiver for endpoint", zap.String("path", path), zap.Error(err)) continue } + w.settings.Logger.Info("splunk_inputs: started receiver for endpoint", zap.String("path", path)) w.mu.Lock() w.active[e.ID] = r w.mu.Unlock() diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index cf932b9..b2b67ce 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" @@ -127,13 +128,20 @@ func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Se func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { var receivers []receiver.Logs for _, input := range inputs { + name := input.Configuration.Stanza.Name disabled := input.Configuration.Stanza.Params.Get("disabled") if disabled != nil && disabled.Value == "1" { + settings.Logger.Info("splunk_inputs: skipping disabled stanza", zap.String("stanza", name)) continue } + settings.Logger.Info("splunk_inputs: creating receiver for stanza", zap.String("stanza", name)) l, err := o.createReceiver(ctx, baseDir, next, input, transforms, props, settings) if err != nil { - return nil, fmt.Errorf("failed to create receiver %q: %w", input.Configuration.Stanza.Name, err) + return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) + } + if l == nil { + settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", name)) + continue } receivers = append(receivers, l) } From bf0a4aa3d0d6c0c1be742cbc3e4709a360678bd4 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 20 Aug 2026 15:43:45 +0200 Subject: [PATCH 15/46] change paths --- internal/tabuilder/tabuilder.go | 34 +++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 77f6d1f..9fc2c58 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -155,6 +155,32 @@ func splunkHomeDirs(splunkHome string) []string { return dirs } +// taDirs returns the conf search path for a single TA directory (default/ then local/). +func taDirs(taDir string) []string { + return []string{ + filepath.Join(taDir, "default"), + filepath.Join(taDir, "local"), + } +} + +// isSingleTA returns true when the path looks like a single TA directory +// (has a default/ or local/ subdirectory but no etc/apps/ layout). +func isSingleTA(path string) bool { + _, errEtc := os.Stat(filepath.Join(path, "etc", "apps")) + _, errDefault := os.Stat(filepath.Join(path, "default")) + _, errLocal := os.Stat(filepath.Join(path, "local")) + hasEtcApps := errEtc == nil + hasTA := errDefault == nil || errLocal == nil + return hasTA && !hasEtcApps +} + +func confDirs(path string) []string { + if isSingleTA(path) { + return taDirs(path) + } + return splunkHomeDirs(path) +} + func readConfFiles(paths []string) ([][]byte, error) { var payloads [][]byte for _, path := range paths { @@ -174,7 +200,7 @@ func readConfFiles(paths []string) ([][]byte, error) { // standard Splunk precedence. Returns nil (no error) when absent. func ReadInputs(splunkHome string) ([]conf.Input, error) { var layers [][]conf.Input - for _, path := range confFilePaths(splunkHomeDirs(splunkHome), "inputs.conf") { + for _, path := range confFilePaths(confDirs(splunkHome), "inputs.conf") { b, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { continue @@ -195,7 +221,7 @@ func ReadInputs(splunkHome string) ([]conf.Input, error) { // ReadTransforms discovers and merges transforms.conf files from splunkHome // using standard Splunk precedence. Returns nil (no error) when absent. func ReadTransforms(splunkHome string) ([]conf.Transform, error) { - payloads, err := readConfFiles(confFilePaths(splunkHomeDirs(splunkHome), "transforms.conf")) + payloads, err := readConfFiles(confFilePaths(confDirs(splunkHome), "transforms.conf")) if err != nil { return nil, err } @@ -213,7 +239,7 @@ func ReadTransforms(splunkHome string) ([]conf.Transform, error) { // ReadProps discovers and merges props.conf files from splunkHome using // standard Splunk precedence. Returns nil (no error) when absent. func ReadProps(splunkHome string) ([]conf.Prop, error) { - payloads, err := readConfFiles(confFilePaths(splunkHomeDirs(splunkHome), "props.conf")) + payloads, err := readConfFiles(confFilePaths(confDirs(splunkHome), "props.conf")) if err != nil { return nil, err } @@ -231,7 +257,7 @@ func ReadProps(splunkHome string) ([]conf.Prop, error) { // ReadOutputs merges outputs.conf across $SPLUNK_HOME using standard Splunk // precedence. Use HTTPOut (or future TCPOut, etc.) to extract a specific type. func ReadOutputs(splunkHome string) (conf.ConfMap, error) { - payloads, err := readConfFiles(confFilePaths(splunkHomeDirs(splunkHome), "outputs.conf")) + payloads, err := readConfFiles(confFilePaths(confDirs(splunkHome), "outputs.conf")) if err != nil { return nil, err } From 36c2205603f6748eef9240952e410df46405ef99 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 09:08:09 +0200 Subject: [PATCH 16/46] change receiver and subreceiver structure --- pkg/splunkinputsreceiver/receiver.go | 101 ++++++++++++++++++++++ pkg/splunkinputsreceiver/subreceiver.go | 109 ------------------------ 2 files changed, 101 insertions(+), 109 deletions(-) diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index eeef5e4..ee96c01 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "os" + "strings" "sync" "go.opentelemetry.io/collector/component" @@ -17,9 +18,109 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" + "github.com/splunk/tarunner/internal/stanza" "github.com/splunk/tarunner/internal/tabuilder" ) +type factoryOptions struct { + subReceivers map[string]SubReceiverFactory +} + +func newFactoryOptions(opts ...Option) factoryOptions { + options := factoryOptions{ + subReceivers: map[string]SubReceiverFactory{}, + } + for _, opt := range opts { + if opt != nil { + opt(&options) + } + } + return options +} + +func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { + cfg := config.(Config) + + if len(cfg.WatchObservers) > 0 { + if cfg.BaseDir != "" || cfg.Path != "" { + return nil, fmt.Errorf("splunk_inputs: watch_observers is mutually exclusive with base_dir and path") + } + return &watchingReceiver{ + cfg: cfg, + settings: settings, + logs: logs, + opts: o, + active: make(map[observer.EndpointID]receiver.Logs), + }, nil + } + + taDir, err := resolveTADir(cfg) + if err != nil { + return nil, err + } + inputs, err := tabuilder.ReadInputs(taDir) + if err != nil { + return nil, err + } + transforms, err := tabuilder.ReadTransforms(taDir) + if err != nil { + return nil, err + } + props, err := tabuilder.ReadProps(taDir) + if err != nil { + return nil, err + } + receivers, err := o.createReceivers(ctx, inputs, transforms, props, taDir, logs, settings) + if err != nil { + return nil, err + } + return packReceivers(receivers), nil +} + +func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { + var receivers []receiver.Logs + for _, input := range inputs { + name := input.Configuration.Stanza.Name + disabled := input.Configuration.Stanza.Params.Get("disabled") + if disabled != nil && disabled.Value == "1" { + settings.Logger.Info("splunk_inputs: skipping disabled stanza", zap.String("stanza", name)) + continue + } + settings.Logger.Info("splunk_inputs: creating receiver for stanza", zap.String("stanza", name)) + l, err := o.createReceiver(ctx, baseDir, next, input, transforms, props, settings) + if err != nil { + return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) + } + if l == nil { + settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", name)) + continue + } + receivers = append(receivers, l) + } + return receivers, nil +} + +func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next consumer.Logs, input Input, transforms []Transform, props []Prop, settings receiver.Settings) (receiver.Logs, error) { + parsed, err := stanza.ParseName(input.Configuration.Stanza.Name) + if err != nil { + return nil, err + } + scheme := parsed.Kind + if scheme == "" { + scheme = "script" + } + if f, ok := o.subReceivers[strings.ToLower(scheme)]; ok { + return f.CreateLogs(ctx, settings, ReceiverRequest{ + BaseDir: baseDir, + Path: parsed.Target, + Input: input, + Transforms: transforms, + Props: props, + }, next) + } + return tabuilder.CreateReceiver(ctx, baseDir, next, input, transforms, props, settings.TelemetrySettings) +} + var nopInstance = &nopReceiver{} type nopReceiver struct { diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index b2b67ce..643d729 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -5,19 +5,12 @@ package splunkinputsreceiver import ( "context" - "fmt" "strings" - "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" - "go.uber.org/zap" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" "github.com/splunk/tarunner/internal/conf" - "github.com/splunk/tarunner/internal/stanza" - "github.com/splunk/tarunner/internal/tabuilder" ) type ( @@ -66,105 +59,3 @@ func WithSubReceiver(f SubReceiverFactory) Option { o.subReceivers[strings.ToLower(f.Scheme())] = f } } - -type factoryOptions struct { - subReceivers map[string]SubReceiverFactory -} - -func newFactoryOptions(opts ...Option) factoryOptions { - options := factoryOptions{ - subReceivers: map[string]SubReceiverFactory{}, - } - - for _, opt := range opts { - if opt != nil { - opt(&options) - } - } - return options -} - -func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { - cfg := config.(Config) - - if len(cfg.WatchObservers) > 0 { - if cfg.BaseDir != "" || cfg.Path != "" { - return nil, fmt.Errorf("splunk_inputs: watch_observers is mutually exclusive with base_dir and path") - } - return &watchingReceiver{ - cfg: cfg, - settings: settings, - logs: logs, - opts: o, - active: make(map[observer.EndpointID]receiver.Logs), - }, nil - } - - taDir, err := resolveTADir(cfg) - if err != nil { - return nil, err - } - - inputs, err := tabuilder.ReadInputs(taDir) - if err != nil { - return nil, err - } - transforms, err := tabuilder.ReadTransforms(taDir) - if err != nil { - return nil, err - } - props, err := tabuilder.ReadProps(taDir) - if err != nil { - return nil, err - } - - receivers, err := o.createReceivers(ctx, inputs, transforms, props, taDir, logs, settings) - if err != nil { - return nil, err - } - return packReceivers(receivers), nil -} - -func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { - var receivers []receiver.Logs - for _, input := range inputs { - name := input.Configuration.Stanza.Name - disabled := input.Configuration.Stanza.Params.Get("disabled") - if disabled != nil && disabled.Value == "1" { - settings.Logger.Info("splunk_inputs: skipping disabled stanza", zap.String("stanza", name)) - continue - } - settings.Logger.Info("splunk_inputs: creating receiver for stanza", zap.String("stanza", name)) - l, err := o.createReceiver(ctx, baseDir, next, input, transforms, props, settings) - if err != nil { - return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) - } - if l == nil { - settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", name)) - continue - } - receivers = append(receivers, l) - } - return receivers, nil -} - -func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next consumer.Logs, input Input, transforms []Transform, props []Prop, settings receiver.Settings) (receiver.Logs, error) { - parsed, err := stanza.ParseName(input.Configuration.Stanza.Name) - if err != nil { - return nil, err - } - scheme := parsed.Kind - if scheme == "" { - scheme = "script" - } - if f, ok := o.subReceivers[scheme]; ok { - return f.CreateLogs(ctx, settings, ReceiverRequest{ - BaseDir: baseDir, - Path: parsed.Target, - Input: input, - Transforms: transforms, - Props: props, - }, next) - } - return tabuilder.CreateReceiver(ctx, baseDir, next, input, transforms, props, settings.TelemetrySettings) -} From ec69387b79f1e8018595d8e58fef2a4b9216b9a9 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:03:12 +0200 Subject: [PATCH 17/46] delete old testdata, change check for unsupported stanzas --- .../testdata/disabled/bin/darwin_arm64/foo | 22 ---------------- .../testdata/disabled/bin/linux_amd64/foo | 22 ---------------- .../testdata/disabled/default/inputs.conf | 5 ---- .../disabled_interval/bin/darwin_arm64/foo | 22 ---------------- .../disabled_interval/bin/linux_amd64/foo | 22 ---------------- .../disabled_interval/default/inputs.conf | 5 ---- .../testdata/periodic/bin/darwin_arm64/foo | 13 ---------- .../testdata/periodic/bin/linux_amd64/foo | 13 ---------- .../testdata/periodic/default/inputs.conf | 5 ---- internal/collector/testdata/script/bin/foo.sh | 13 ---------- .../testdata/script/default/inputs.conf | 5 ---- .../testdata/ta/bin/darwin_arm64/foo | 13 ---------- .../collector/testdata/ta/bin/linux_amd64/foo | 13 ---------- .../collector/testdata/ta/default/inputs.conf | 5 ---- .../testdata/tcp/default/inputs.conf | 1 - .../testdata/udp/default/inputs.conf | 1 - internal/tabuilder/tabuilder.go | 25 ++++++++++--------- pkg/splunkinputsreceiver/receiver.go | 11 ++++---- 18 files changed, 19 insertions(+), 197 deletions(-) delete mode 100755 internal/collector/testdata/disabled/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/disabled/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/disabled/default/inputs.conf delete mode 100755 internal/collector/testdata/disabled_interval/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/disabled_interval/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/disabled_interval/default/inputs.conf delete mode 100755 internal/collector/testdata/periodic/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/periodic/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/periodic/default/inputs.conf delete mode 100755 internal/collector/testdata/script/bin/foo.sh delete mode 100644 internal/collector/testdata/script/default/inputs.conf delete mode 100755 internal/collector/testdata/ta/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/ta/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/ta/default/inputs.conf delete mode 100644 internal/collector/testdata/tcp/default/inputs.conf delete mode 100644 internal/collector/testdata/udp/default/inputs.conf diff --git a/internal/collector/testdata/disabled/bin/darwin_arm64/foo b/internal/collector/testdata/disabled/bin/darwin_arm64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled/bin/darwin_arm64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled/bin/linux_amd64/foo b/internal/collector/testdata/disabled/bin/linux_amd64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled/bin/linux_amd64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled/default/inputs.conf b/internal/collector/testdata/disabled/default/inputs.conf deleted file mode 100644 index 8d0899e..0000000 --- a/internal/collector/testdata/disabled/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -disabled = 1 -interval = 20 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/disabled_interval/bin/darwin_arm64/foo b/internal/collector/testdata/disabled_interval/bin/darwin_arm64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled_interval/bin/darwin_arm64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled_interval/bin/linux_amd64/foo b/internal/collector/testdata/disabled_interval/bin/linux_amd64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled_interval/bin/linux_amd64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled_interval/default/inputs.conf b/internal/collector/testdata/disabled_interval/default/inputs.conf deleted file mode 100644 index 41a5db9..0000000 --- a/internal/collector/testdata/disabled_interval/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -# Required parameters for the application to function: -interval = -1 # disabled -sourcetype = _foo -index = diff --git a/internal/collector/testdata/periodic/bin/darwin_arm64/foo b/internal/collector/testdata/periodic/bin/darwin_arm64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/periodic/bin/darwin_arm64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/periodic/bin/linux_amd64/foo b/internal/collector/testdata/periodic/bin/linux_amd64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/periodic/bin/linux_amd64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/periodic/default/inputs.conf b/internal/collector/testdata/periodic/default/inputs.conf deleted file mode 100644 index c530bf7..0000000 --- a/internal/collector/testdata/periodic/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -# Required parameters for the application to function: -interval = 1 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/script/bin/foo.sh b/internal/collector/testdata/script/bin/foo.sh deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/script/bin/foo.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/script/default/inputs.conf b/internal/collector/testdata/script/default/inputs.conf deleted file mode 100644 index 3f6a5a3..0000000 --- a/internal/collector/testdata/script/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[script://./bin/foo.sh] -# Required parameters for the application to function: -interval = 0 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/ta/bin/darwin_arm64/foo b/internal/collector/testdata/ta/bin/darwin_arm64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/ta/bin/darwin_arm64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/ta/bin/linux_amd64/foo b/internal/collector/testdata/ta/bin/linux_amd64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/ta/bin/linux_amd64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/ta/default/inputs.conf b/internal/collector/testdata/ta/default/inputs.conf deleted file mode 100644 index fd2b52d..0000000 --- a/internal/collector/testdata/ta/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -# Required parameters for the application to function: -interval = 0 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/tcp/default/inputs.conf b/internal/collector/testdata/tcp/default/inputs.conf deleted file mode 100644 index 640b781..0000000 --- a/internal/collector/testdata/tcp/default/inputs.conf +++ /dev/null @@ -1 +0,0 @@ -[tcp://localhost:4000] diff --git a/internal/collector/testdata/udp/default/inputs.conf b/internal/collector/testdata/udp/default/inputs.conf deleted file mode 100644 index 2ad4d88..0000000 --- a/internal/collector/testdata/udp/default/inputs.conf +++ /dev/null @@ -1 +0,0 @@ -[udp://localhost:4000] diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 9fc2c58..b351c00 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -196,11 +196,12 @@ func readConfFiles(paths []string) ([][]byte, error) { return payloads, nil } -// ReadInputs discovers and merges inputs.conf files from splunkHome using -// standard Splunk precedence. Returns nil (no error) when absent. -func ReadInputs(splunkHome string) ([]conf.Input, error) { +// ReadInputs discovers and merges inputs.conf files from dir using standard +// Splunk precedence. dir may be a Splunk home or a single TA directory. +// Returns nil (no error) when absent. +func ReadInputs(dir string) ([]conf.Input, error) { var layers [][]conf.Input - for _, path := range confFilePaths(confDirs(splunkHome), "inputs.conf") { + for _, path := range confFilePaths(confDirs(dir), "inputs.conf") { b, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { continue @@ -218,10 +219,10 @@ func ReadInputs(splunkHome string) ([]conf.Input, error) { return conf.MergeInputs(layers), nil } -// ReadTransforms discovers and merges transforms.conf files from splunkHome -// using standard Splunk precedence. Returns nil (no error) when absent. -func ReadTransforms(splunkHome string) ([]conf.Transform, error) { - payloads, err := readConfFiles(confFilePaths(confDirs(splunkHome), "transforms.conf")) +// ReadTransforms discovers and merges transforms.conf files from dir using +// standard Splunk precedence. Returns nil (no error) when absent. +func ReadTransforms(dir string) ([]conf.Transform, error) { + payloads, err := readConfFiles(confFilePaths(confDirs(dir), "transforms.conf")) if err != nil { return nil, err } @@ -236,10 +237,10 @@ func ReadTransforms(splunkHome string) ([]conf.Transform, error) { return conf.MergeTransforms(layers), nil } -// ReadProps discovers and merges props.conf files from splunkHome using -// standard Splunk precedence. Returns nil (no error) when absent. -func ReadProps(splunkHome string) ([]conf.Prop, error) { - payloads, err := readConfFiles(confFilePaths(confDirs(splunkHome), "props.conf")) +// ReadProps discovers and merges props.conf files from dir using standard +// Splunk precedence. Returns nil (no error) when absent. +func ReadProps(dir string) ([]conf.Prop, error) { + payloads, err := readConfFiles(confFilePaths(confDirs(dir), "props.conf")) if err != nil { return nil, err } diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index ee96c01..8a8e278 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -91,10 +91,6 @@ func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, tra if err != nil { return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) } - if l == nil { - settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", name)) - continue - } receivers = append(receivers, l) } return receivers, nil @@ -118,7 +114,12 @@ func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next Props: props, }, next) } - return tabuilder.CreateReceiver(ctx, baseDir, next, input, transforms, props, settings.TelemetrySettings) + l, err := tabuilder.CreateReceiver(ctx, baseDir, next, input, transforms, props, settings.TelemetrySettings) + if l == nil && err == nil { + settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", input.Configuration.Stanza.Name)) + return nopInstance, nil + } + return l, err } var nopInstance = &nopReceiver{} From e33616149e0d097ec7631aef090d226331c5fb80 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:45:26 +0200 Subject: [PATCH 18/46] add check for the directory if it is a TA --- pkg/folderobserver/extension.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/folderobserver/extension.go b/pkg/folderobserver/extension.go index c3b2130..8e170fc 100644 --- a/pkg/folderobserver/extension.go +++ b/pkg/folderobserver/extension.go @@ -68,7 +68,11 @@ func (l *folderLister) ListEndpoints() []observer.Endpoint { continue } absPath := filepath.Join(l.path, entry.Name()) - l.logger.Info("folder_observer: discovered directory", zap.String("path", absPath)) + appConf := filepath.Join(absPath, "default", "app.conf") + if _, err := os.Stat(appConf); err != nil { + continue + } + l.logger.Info("folder_observer: discovered addon", zap.String("path", absPath)) endpoints = append(endpoints, observer.Endpoint{ ID: observer.EndpointID(absPath), Target: absPath, From 1d258eafec438bd4f01e4e3a31ad99c11da62910 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 10:59:12 +0200 Subject: [PATCH 19/46] add check for the directory if it is a TA --- pkg/folderobserver/extension.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/folderobserver/extension.go b/pkg/folderobserver/extension.go index 8e170fc..e81dd21 100644 --- a/pkg/folderobserver/extension.go +++ b/pkg/folderobserver/extension.go @@ -67,11 +67,10 @@ func (l *folderLister) ListEndpoints() []observer.Endpoint { if !entry.IsDir() { continue } - absPath := filepath.Join(l.path, entry.Name()) - appConf := filepath.Join(absPath, "default", "app.conf") - if _, err := os.Stat(appConf); err != nil { + if !strings.HasPrefix(entry.Name(), "Splunk_TA") { continue } + absPath := filepath.Join(l.path, entry.Name()) l.logger.Info("folder_observer: discovered addon", zap.String("path", absPath)) endpoints = append(endpoints, observer.Endpoint{ ID: observer.EndpointID(absPath), From 156f5849074b08af06280a594c0f9b9da822adb4 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:08:48 +0200 Subject: [PATCH 20/46] add check for the directory if it is a TA --- pkg/folderobserver/extension.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/folderobserver/extension.go b/pkg/folderobserver/extension.go index e81dd21..689c7ab 100644 --- a/pkg/folderobserver/extension.go +++ b/pkg/folderobserver/extension.go @@ -7,6 +7,7 @@ import ( "context" "os" "path/filepath" + "strings" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/extension" @@ -67,7 +68,7 @@ func (l *folderLister) ListEndpoints() []observer.Endpoint { if !entry.IsDir() { continue } - if !strings.HasPrefix(entry.Name(), "Splunk_TA") { + if !(strings.HasPrefix(entry.Name(), "Splunk_TA")) { continue } absPath := filepath.Join(l.path, entry.Name()) From 723e8a30601664d49cd8ab86d017ccfb57ebb90f Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:21:23 +0200 Subject: [PATCH 21/46] add check for script permissions --- internal/scriptedinput/input.go | 40 ++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/internal/scriptedinput/input.go b/internal/scriptedinput/input.go index 1320464..95d00a3 100644 --- a/internal/scriptedinput/input.go +++ b/internal/scriptedinput/input.go @@ -5,8 +5,10 @@ package scriptedinput import ( "context" + "errors" "fmt" "io" + "os" "os/exec" "strconv" "syscall" @@ -86,19 +88,24 @@ func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) case <-si.doneChan: return default: - si.execute(baseDir, input) + if !si.execute(baseDir, input) { + return + } } } }() } else { interval := time.Duration(intervalS) * time.Second go func() { - si.execute(baseDir, input) - + if !si.execute(baseDir, input) { + return + } for { select { case <-time.After(interval): - si.execute(baseDir, input) + if !si.execute(baseDir, input) { + return + } case <-si.doneChan: return } @@ -108,10 +115,31 @@ func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) return true, nil } -func (si *ScriptedInput) execute(baseDir string, input conf.Input) { +// execute runs the script and returns false if the error is permanent +// (e.g. exec format error) and the input should not be retried. +func (si *ScriptedInput) execute(baseDir string, input conf.Input) bool { if err := si._execute(baseDir, input); err != nil { - si.logger.Error("Error executing input", zap.String("input", input.Configuration.Stanza.Name), zap.String("error", err.Error())) + if isPermanentExecError(err) { + si.logger.Warn("Skipping input permanently due to exec error", zap.String("input", input.Configuration.Stanza.Name), zap.Error(err)) + return false + } + si.logger.Error("Error executing input", zap.String("input", input.Configuration.Stanza.Name), zap.Error(err)) + } + return true +} + +func isPermanentExecError(err error) bool { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + return status.Signal() == syscall.SIGILL + } + } + var pathErr *os.PathError + if errors.As(err, &pathErr) { + return errors.Is(pathErr.Err, syscall.ENOEXEC) || errors.Is(pathErr.Err, syscall.EACCES) } + return false } func (si *ScriptedInput) _execute(baseDir string, input conf.Input) error { From 3e078c22f7fb55dd170306c066c44d2c5e77fadb Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 11:45:01 +0200 Subject: [PATCH 22/46] bring back functions refactor --- pkg/splunkinputsreceiver/receiver.go | 102 ---------------------- pkg/splunkinputsreceiver/subreceiver.go | 110 ++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 102 deletions(-) diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index 8a8e278..eeef5e4 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -8,7 +8,6 @@ import ( "errors" "fmt" "os" - "strings" "sync" "go.opentelemetry.io/collector/component" @@ -18,110 +17,9 @@ import ( "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" - "github.com/splunk/tarunner/internal/stanza" "github.com/splunk/tarunner/internal/tabuilder" ) -type factoryOptions struct { - subReceivers map[string]SubReceiverFactory -} - -func newFactoryOptions(opts ...Option) factoryOptions { - options := factoryOptions{ - subReceivers: map[string]SubReceiverFactory{}, - } - for _, opt := range opts { - if opt != nil { - opt(&options) - } - } - return options -} - -func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { - cfg := config.(Config) - - if len(cfg.WatchObservers) > 0 { - if cfg.BaseDir != "" || cfg.Path != "" { - return nil, fmt.Errorf("splunk_inputs: watch_observers is mutually exclusive with base_dir and path") - } - return &watchingReceiver{ - cfg: cfg, - settings: settings, - logs: logs, - opts: o, - active: make(map[observer.EndpointID]receiver.Logs), - }, nil - } - - taDir, err := resolveTADir(cfg) - if err != nil { - return nil, err - } - inputs, err := tabuilder.ReadInputs(taDir) - if err != nil { - return nil, err - } - transforms, err := tabuilder.ReadTransforms(taDir) - if err != nil { - return nil, err - } - props, err := tabuilder.ReadProps(taDir) - if err != nil { - return nil, err - } - receivers, err := o.createReceivers(ctx, inputs, transforms, props, taDir, logs, settings) - if err != nil { - return nil, err - } - return packReceivers(receivers), nil -} - -func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { - var receivers []receiver.Logs - for _, input := range inputs { - name := input.Configuration.Stanza.Name - disabled := input.Configuration.Stanza.Params.Get("disabled") - if disabled != nil && disabled.Value == "1" { - settings.Logger.Info("splunk_inputs: skipping disabled stanza", zap.String("stanza", name)) - continue - } - settings.Logger.Info("splunk_inputs: creating receiver for stanza", zap.String("stanza", name)) - l, err := o.createReceiver(ctx, baseDir, next, input, transforms, props, settings) - if err != nil { - return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) - } - receivers = append(receivers, l) - } - return receivers, nil -} - -func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next consumer.Logs, input Input, transforms []Transform, props []Prop, settings receiver.Settings) (receiver.Logs, error) { - parsed, err := stanza.ParseName(input.Configuration.Stanza.Name) - if err != nil { - return nil, err - } - scheme := parsed.Kind - if scheme == "" { - scheme = "script" - } - if f, ok := o.subReceivers[strings.ToLower(scheme)]; ok { - return f.CreateLogs(ctx, settings, ReceiverRequest{ - BaseDir: baseDir, - Path: parsed.Target, - Input: input, - Transforms: transforms, - Props: props, - }, next) - } - l, err := tabuilder.CreateReceiver(ctx, baseDir, next, input, transforms, props, settings.TelemetrySettings) - if l == nil && err == nil { - settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", input.Configuration.Stanza.Name)) - return nopInstance, nil - } - return l, err -} - var nopInstance = &nopReceiver{} type nopReceiver struct { diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 643d729..8308f88 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -5,12 +5,19 @@ package splunkinputsreceiver import ( "context" + "fmt" "strings" + "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" + + "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" "github.com/splunk/tarunner/internal/conf" + "github.com/splunk/tarunner/internal/stanza" + "github.com/splunk/tarunner/internal/tabuilder" ) type ( @@ -59,3 +66,106 @@ func WithSubReceiver(f SubReceiverFactory) Option { o.subReceivers[strings.ToLower(f.Scheme())] = f } } + +type factoryOptions struct { + subReceivers map[string]SubReceiverFactory +} + +func newFactoryOptions(opts ...Option) factoryOptions { + options := factoryOptions{ + subReceivers: map[string]SubReceiverFactory{}, + } + + for _, opt := range opts { + if opt != nil { + opt(&options) + } + } + return options +} + +func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { + cfg := config.(Config) + + if len(cfg.WatchObservers) > 0 { + if cfg.BaseDir != "" || cfg.Path != "" { + return nil, fmt.Errorf("splunk_inputs: watch_observers is mutually exclusive with base_dir and path") + } + return &watchingReceiver{ + cfg: cfg, + settings: settings, + logs: logs, + opts: o, + active: make(map[observer.EndpointID]receiver.Logs), + }, nil + } + + taDir, err := resolveTADir(cfg) + if err != nil { + return nil, err + } + + inputs, err := tabuilder.ReadInputs(taDir) + if err != nil { + return nil, err + } + transforms, err := tabuilder.ReadTransforms(taDir) + if err != nil { + return nil, err + } + props, err := tabuilder.ReadProps(taDir) + if err != nil { + return nil, err + } + + receivers, err := o.createReceivers(ctx, inputs, transforms, props, taDir, logs, settings) + if err != nil { + return nil, err + } + return packReceivers(receivers), nil +} + +func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { + var receivers []receiver.Logs + for _, input := range inputs { + name := input.Configuration.Stanza.Name + disabled := input.Configuration.Stanza.Params.Get("disabled") + if disabled != nil && disabled.Value == "1" { + settings.Logger.Info("splunk_inputs: skipping disabled stanza", zap.String("stanza", name)) + continue + } + settings.Logger.Info("splunk_inputs: creating receiver for stanza", zap.String("stanza", name)) + l, err := o.createReceiver(ctx, baseDir, next, input, transforms, props, settings) + if err != nil { + return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) + } + receivers = append(receivers, l) + } + return receivers, nil +} + +func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next consumer.Logs, input Input, transforms []Transform, props []Prop, settings receiver.Settings) (receiver.Logs, error) { + parsed, err := stanza.ParseName(input.Configuration.Stanza.Name) + if err != nil { + return nil, err + } + scheme := parsed.Kind + if scheme == "" { + scheme = "script" + } + if f, ok := o.subReceivers[scheme]; ok { + return f.CreateLogs(ctx, settings, ReceiverRequest{ + BaseDir: baseDir, + Path: parsed.Target, + Input: input, + Transforms: transforms, + Props: props, + }, next) + } + l, err := tabuilder.CreateReceiver(ctx, baseDir, next, input, transforms, props, settings.TelemetrySettings) + if l == nil && err == nil { + settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", input.Configuration.Stanza.Name)) + return nopInstance, nil + } + return l, err +} From 02d1e0d229d2ef528b5b1edbe9729cc4953eb41b Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:11:48 +0200 Subject: [PATCH 23/46] delete excessive logging --- pkg/folderobserver/extension.go | 1 - pkg/splunkinputsreceiver/receiver.go | 4 ---- pkg/splunkinputsreceiver/subreceiver.go | 1 - 3 files changed, 6 deletions(-) diff --git a/pkg/folderobserver/extension.go b/pkg/folderobserver/extension.go index 689c7ab..d3df77e 100644 --- a/pkg/folderobserver/extension.go +++ b/pkg/folderobserver/extension.go @@ -79,6 +79,5 @@ func (l *folderLister) ListEndpoints() []observer.Endpoint { Details: &folderDetails{Name: entry.Name(), Path: absPath}, }) } - l.logger.Info("folder_observer: scan complete", zap.String("path", l.path), zap.Int("count", len(endpoints))) return endpoints } diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index eeef5e4..799fc2f 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -95,9 +95,7 @@ func (w *watchingReceiver) Start(ctx context.Context, host component.Host) error if !ok { return fmt.Errorf("splunk_inputs: extension %q does not implement observer.Observable", obsID) } - w.settings.Logger.Info("splunk_inputs: calling ListAndWatch", zap.String("observer", obsID.String())) obs.ListAndWatch(w) - w.settings.Logger.Info("splunk_inputs: ListAndWatch returned", zap.String("observer", obsID.String())) } return nil } @@ -129,7 +127,6 @@ func (w *watchingReceiver) OnAdd(endpoints []observer.Endpoint) { zap.String("path", path), zap.Error(err)) continue } - w.settings.Logger.Info("splunk_inputs: read inputs", zap.String("path", path), zap.Int("count", len(inputs))) transforms, err := tabuilder.ReadTransforms(path) if err != nil { w.settings.Logger.Error("splunk_inputs: failed to read transforms for endpoint", @@ -148,7 +145,6 @@ func (w *watchingReceiver) OnAdd(endpoints []observer.Endpoint) { zap.String("path", path), zap.Error(err)) continue } - w.settings.Logger.Info("splunk_inputs: created receivers for endpoint", zap.String("path", path), zap.Int("count", len(receivers))) r := packReceivers(receivers) if err := r.Start(w.ctx, w.host); err != nil { w.settings.Logger.Error("splunk_inputs: failed to start receiver for endpoint", diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 8308f88..d1a8e8a 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -134,7 +134,6 @@ func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, tra settings.Logger.Info("splunk_inputs: skipping disabled stanza", zap.String("stanza", name)) continue } - settings.Logger.Info("splunk_inputs: creating receiver for stanza", zap.String("stanza", name)) l, err := o.createReceiver(ctx, baseDir, next, input, transforms, props, settings) if err != nil { return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) From 237cf67f2b1b9fd76277a281f4cd7cf6e19436dd Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:39:26 +0200 Subject: [PATCH 24/46] change script path determining logic --- internal/conf/inputs.go | 50 +++++--------------------------------- internal/script/command.go | 11 +++++++-- 2 files changed, 15 insertions(+), 46 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index bb78123..8a93273 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -5,10 +5,6 @@ package conf import ( "encoding/xml" - "fmt" - "path/filepath" - "runtime" - "strings" "gopkg.in/ini.v1" ) @@ -45,6 +41,7 @@ func (p Params) Get(name string) *Param { type Stanza struct { Name string `xml:"name,attr"` App string `xml:"app,attr"` + AppDir string `xml:"-"` // app directory used to resolve relative script paths at execution time Params Params `xml:"param"` } @@ -54,8 +51,9 @@ type Param struct { } // ReadInput parses an inputs.conf payload. appDir is the app directory -// (e.g. $SPLUNK_HOME/etc/apps/my_ta) used to resolve relative script paths -// to absolute paths at parse time. Pass an empty string to skip resolution. +// (e.g. $SPLUNK_HOME/etc/apps/my_ta) stored on each stanza for use when +// resolving relative script paths at execution time. Pass an empty string +// when the app directory is unknown or irrelevant. func ReadInput(payload []byte, appDir string) ([]Input, error) { f, err := ini.Load(payload) if err != nil { @@ -67,15 +65,12 @@ func ReadInput(payload []byte, appDir string) ([]Input, error) { if section.Name() == ini.DefaultSection { continue // disregard default section. We need a stanza per input. } - name := section.Name() - if appDir != "" { - name = resolveInputPath(name, appDir) - } i := Input{ Configuration: Configuration{ Stanza: Stanza{ - Name: name, + Name: section.Name(), App: appName, + AppDir: appDir, Params: make([]Param, len(section.Keys())), }, }, @@ -95,39 +90,6 @@ func ReadInput(payload []byte, appDir string) ([]Input, error) { return result, nil } -// resolveInputPath rewrites a stanza name's relative path component to an -// absolute path using appDir. Only script:// and bare (scripted) stanzas -// are rewritten; monitor://, tcp://, udp://, wineventlog:// are left as-is -// since their targets are not relative to the app directory. -func resolveInputPath(name, appDir string) string { - switch { - case hasScheme(name, "script"): - rel := name[len("script://"):] - return "script://" + absPath(appDir, rel) - case hasScheme(name, "monitor"), hasScheme(name, "tcp"), hasScheme(name, "udp"), hasScheme(name, "wineventlog"), hasScheme(name, "batch"): - return name - default: - // bare scripted input: name is the script filename - abs := absPath(appDir, filepath.Join("bin", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), name)) - return abs - } -} - -func hasScheme(name, scheme string) bool { - return strings.HasPrefix(strings.ToLower(name), scheme+"://") -} - -func absPath(appDir, rel string) string { - if filepath.IsAbs(rel) { - return rel - } - abs, err := filepath.Abs(filepath.Join(appDir, rel)) - if err != nil { - return filepath.Join(appDir, rel) - } - return abs -} - // MergeInputs merges multiple slices of inputs, with later slices taking // precedence over earlier ones (local/ wins over default/). // Stanzas are keyed by name; the last definition of a stanza wins entirely. diff --git a/internal/script/command.go b/internal/script/command.go index da470ce..6f1469e 100644 --- a/internal/script/command.go +++ b/internal/script/command.go @@ -18,6 +18,13 @@ func DetermineCommandName(baseDir string, input conf.Input) (string, error) { if err != nil { return "", err } + // Use AppDir from the stanza when available; it points to the specific app + // directory the conf was parsed from, which is more accurate than baseDir + // when multiple apps are layered together. + resolveDir := baseDir + if input.Configuration.Stanza.AppDir != "" { + resolveDir = input.Configuration.Stanza.AppDir + } switch parsed.Kind { case "monitor", "batch": return parsed.Target, nil @@ -25,12 +32,12 @@ func DetermineCommandName(baseDir string, input conf.Input) (string, error) { if filepath.IsAbs(parsed.Target) { return parsed.Target, nil } - return GetPath(baseDir, parsed.Target) + return GetPath(resolveDir, parsed.Target) case "": if filepath.IsAbs(parsed.Target) { return parsed.Target, nil } - return GetPath(baseDir, filepath.Join("bin", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), parsed.Target)) + return GetPath(resolveDir, filepath.Join("bin", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), parsed.Target)) default: return "", fmt.Errorf("unknown scheme %q", parsed.Kind) } From da4618c01512e06863ffc58ddfd3ad03fe581d65 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Fri, 21 Aug 2026 12:45:04 +0200 Subject: [PATCH 25/46] add function for checking if stanza is disabled --- internal/conf/inputs.go | 6 ++++++ internal/scriptedinput/input.go | 6 +++--- internal/tabuilder/tabuilder.go | 3 +-- pkg/splunkinputsreceiver/subreceiver.go | 3 +-- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index 8a93273..8da6253 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -50,6 +50,12 @@ type Param struct { Value string `xml:",innerxml"` } +// IsDisabled reports whether the stanza has disabled=1. +func (s *Stanza) IsDisabled() bool { + p := s.Params.Get("disabled") + return p != nil && p.Value == "1" +} + // ReadInput parses an inputs.conf payload. appDir is the app directory // (e.g. $SPLUNK_HOME/etc/apps/my_ta) stored on each stanza for use when // resolving relative script paths at execution time. Pass an empty string diff --git a/internal/scriptedinput/input.go b/internal/scriptedinput/input.go index 95d00a3..7a12524 100644 --- a/internal/scriptedinput/input.go +++ b/internal/scriptedinput/input.go @@ -65,6 +65,9 @@ func (si *ScriptedInput) scheduleInput(baseDir string, input conf.Input) (bool, } func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) (bool, error) { + if input.Configuration.Stanza.IsDisabled() { + return false, nil + } intervalS := 3600 for _, p := range input.Configuration.Stanza.Params { if p.Name == "interval" { @@ -74,9 +77,6 @@ func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) return false, err } } - if p.Name == "disabled" && p.Value == "1" { - return false, nil - } } if intervalS == -1 { return false, nil diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index b351c00..cbfadfa 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -44,8 +44,7 @@ import ( func CreateReceivers(ctx context.Context, inputs []conf.Input, transforms []conf.Transform, props []conf.Prop, baseDir string, next consumer.Logs, telemetrySettings component.TelemetrySettings) ([]receiver.Logs, error) { var receivers []receiver.Logs for _, input := range inputs { - disabled := input.Configuration.Stanza.Params.Get("disabled") - if disabled != nil && disabled.Value == "1" { + if input.Configuration.Stanza.IsDisabled() { continue } l, err := CreateReceiver(ctx, baseDir, next, input, transforms, props, telemetrySettings) diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index d1a8e8a..dbb1772 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -129,8 +129,7 @@ func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, tra var receivers []receiver.Logs for _, input := range inputs { name := input.Configuration.Stanza.Name - disabled := input.Configuration.Stanza.Params.Get("disabled") - if disabled != nil && disabled.Value == "1" { + if input.Configuration.Stanza.IsDisabled() { settings.Logger.Info("splunk_inputs: skipping disabled stanza", zap.String("stanza", name)) continue } From f92ad387425a4ec2c7d2c72246ad63520a6a586e Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Mon, 24 Aug 2026 11:22:41 +0200 Subject: [PATCH 26/46] delete local folder observer --- pkg/folderobserver/config.go | 17 --- pkg/folderobserver/doc.go | 4 - pkg/folderobserver/extension.go | 83 --------------- pkg/folderobserver/factory.go | 33 ------ pkg/folderobserver/go.mod | 25 ----- pkg/folderobserver/go.sum | 70 ------------- pkg/splunkinputsreceiver/config.go | 12 +-- pkg/splunkinputsreceiver/factory_test.go | 4 +- pkg/splunkinputsreceiver/go.mod | 1 - pkg/splunkinputsreceiver/go.sum | 2 - pkg/splunkinputsreceiver/receiver.go | 127 ----------------------- pkg/splunkinputsreceiver/subreceiver.go | 18 +--- 12 files changed, 6 insertions(+), 390 deletions(-) delete mode 100644 pkg/folderobserver/config.go delete mode 100644 pkg/folderobserver/doc.go delete mode 100644 pkg/folderobserver/extension.go delete mode 100644 pkg/folderobserver/factory.go delete mode 100644 pkg/folderobserver/go.mod delete mode 100644 pkg/folderobserver/go.sum diff --git a/pkg/folderobserver/config.go b/pkg/folderobserver/config.go deleted file mode 100644 index f20b3ae..0000000 --- a/pkg/folderobserver/config.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package folderobserver - -import "time" - -// Config defines configuration for the folder observer extension. -type Config struct { - // Path is the directory to watch for subdirectories. Each subdirectory - // found will be emitted as a "folder" endpoint. - Path string `mapstructure:"path"` - - // RefreshInterval controls how often the directory is re-scanned for - // added or removed subdirectories. - RefreshInterval time.Duration `mapstructure:"refresh_interval"` -} diff --git a/pkg/folderobserver/doc.go b/pkg/folderobserver/doc.go deleted file mode 100644 index 6310564..0000000 --- a/pkg/folderobserver/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package folderobserver diff --git a/pkg/folderobserver/extension.go b/pkg/folderobserver/extension.go deleted file mode 100644 index d3df77e..0000000 --- a/pkg/folderobserver/extension.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package folderobserver - -import ( - "context" - "os" - "path/filepath" - "strings" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/extension" - "go.uber.org/zap" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer/endpointswatcher" -) - -// folderDetails holds details about a discovered directory endpoint. -type folderDetails struct { - Name string - Path string -} - -func (f *folderDetails) Type() observer.EndpointType { return observer.EndpointType("folder") } -func (f *folderDetails) Env() observer.EndpointEnv { - return observer.EndpointEnv{"name": f.Name, "path": f.Path} -} - -type folderObserver struct { - *endpointswatcher.EndpointsWatcher -} - -var _ extension.Extension = (*folderObserver)(nil) - -func newObserver(params extension.Settings, config *Config) (extension.Extension, error) { - return &folderObserver{ - EndpointsWatcher: endpointswatcher.New( - &folderLister{path: config.Path, logger: params.Logger}, - config.RefreshInterval, - params.Logger, - ), - }, nil -} - -func (*folderObserver) Start(context.Context, component.Host) error { return nil } - -func (o *folderObserver) Shutdown(context.Context) error { - o.StopListAndWatch() - return nil -} - -type folderLister struct { - path string - logger *zap.Logger -} - -func (l *folderLister) ListEndpoints() []observer.Endpoint { - entries, err := os.ReadDir(l.path) - if err != nil { - l.logger.Error("folder_observer: failed to read directory", zap.String("path", l.path), zap.Error(err)) - return nil - } - - var endpoints []observer.Endpoint - for _, entry := range entries { - if !entry.IsDir() { - continue - } - if !(strings.HasPrefix(entry.Name(), "Splunk_TA")) { - continue - } - absPath := filepath.Join(l.path, entry.Name()) - l.logger.Info("folder_observer: discovered addon", zap.String("path", absPath)) - endpoints = append(endpoints, observer.Endpoint{ - ID: observer.EndpointID(absPath), - Target: absPath, - Details: &folderDetails{Name: entry.Name(), Path: absPath}, - }) - } - return endpoints -} diff --git a/pkg/folderobserver/factory.go b/pkg/folderobserver/factory.go deleted file mode 100644 index 56a5c58..0000000 --- a/pkg/folderobserver/factory.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright Splunk, Inc. -// SPDX-License-Identifier: Apache-2.0 - -package folderobserver - -import ( - "context" - "time" - - "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/extension" -) - -const defaultRefreshInterval = 60 * time.Second - -func NewFactory() extension.Factory { - return extension.NewFactory( - component.MustNewType("folder_observer"), - createDefaultConfig, - createExtension, - component.StabilityLevelDevelopment, - ) -} - -func createDefaultConfig() component.Config { - return &Config{ - RefreshInterval: defaultRefreshInterval, - } -} - -func createExtension(_ context.Context, params extension.Settings, cfg component.Config) (extension.Extension, error) { - return newObserver(params, cfg.(*Config)) -} diff --git a/pkg/folderobserver/go.mod b/pkg/folderobserver/go.mod deleted file mode 100644 index 27946d4..0000000 --- a/pkg/folderobserver/go.mod +++ /dev/null @@ -1,25 +0,0 @@ -module github.com/splunk/tarunner/pkg/folderobserver - -go 1.26.4 - -require ( - github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 - go.opentelemetry.io/collector/component v1.63.0 - go.opentelemetry.io/collector/extension v1.63.0 - go.uber.org/zap v1.28.0 -) - -require ( - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/hashicorp/go-version v1.9.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - go.opentelemetry.io/collector/featuregate v1.63.0 // indirect - go.opentelemetry.io/collector/internal/componentalias v0.157.0 // indirect - go.opentelemetry.io/collector/pdata v1.63.0 // indirect - go.opentelemetry.io/otel v1.44.0 // indirect - go.opentelemetry.io/otel/metric v1.44.0 // indirect - go.opentelemetry.io/otel/trace v1.44.0 // indirect - go.uber.org/multierr v1.11.0 // indirect -) diff --git a/pkg/folderobserver/go.sum b/pkg/folderobserver/go.sum deleted file mode 100644 index 833af67..0000000 --- a/pkg/folderobserver/go.sum +++ /dev/null @@ -1,70 +0,0 @@ -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= -github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/collector/component v1.63.0 h1:l98ZCxfCTt/O6dYB0JVKKtewaFLe/a6N2qQe61Tbf2o= -go.opentelemetry.io/collector/component v1.63.0/go.mod h1:yLGMmT7jUiqvuGvkqlfR1CBi0dRkSV67tq22I08ZMPk= -go.opentelemetry.io/collector/extension v1.63.0 h1:wtZXWEFo4pM5Nw/ZUTiC1JbhG0rfbX4n+4nObN1RD9Y= -go.opentelemetry.io/collector/extension v1.63.0/go.mod h1:K4UQiO/T+B3ex5D5UL0H0Jd7xB3NL12qUHisGiCitbU= -go.opentelemetry.io/collector/featuregate v1.63.0 h1:6EWX1C5AtmIh8hFH97DwK6R7R8Jk3fTLxAUfZPXGutY= -go.opentelemetry.io/collector/featuregate v1.63.0/go.mod h1:4ga1QBMPEejXXmpyJS8lmaRpknJ3Lb9Bvk6e420bUFU= -go.opentelemetry.io/collector/internal/componentalias v0.157.0 h1:bvVANHVkXRhoRBc92rDsbiC0OHlu9/EbRqPROD9qrPc= -go.opentelemetry.io/collector/internal/componentalias v0.157.0/go.mod h1:PCLANRXGlMhf9NmU+JPFHtZuY4WpMOsccXlDHTDTu1w= -go.opentelemetry.io/collector/internal/testutil v0.157.0 h1:plojUQwFC5l1ex9KUDaLmCFY/mTxEmf3zrlP7M23IEw= -go.opentelemetry.io/collector/internal/testutil v0.157.0/go.mod h1:Jkjs6rkqs973LqgZ0Fe3zrokQRKULYXPIf4HuqStiEE= -go.opentelemetry.io/collector/pdata v1.63.0 h1:fY2xSG2MnyoBwA4GUhzoogGZMuNS0qHpCoODqaKwiVQ= -go.opentelemetry.io/collector/pdata v1.63.0/go.mod h1:jzozYYhQEkTQ/CCbCBNC+hYUeju9S2J8HIqIDHdxZWk= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= -go.opentelemetry.io/proto/slim/otlp v1.10.0 h1:iR97Vs/ZDR+y9TfuP9b1XBtdPWeC+OMslIBmhcLU7jM= -go.opentelemetry.io/proto/slim/otlp v1.10.0/go.mod h1:lV9250stpjYLPNA5viFabIgP2QlUGRT1GdTgAf8SIUk= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0 h1:RUF5rO0hAlgiJt1fzQVzcVs3vZVNHIcMLgOgG4rWNcQ= -go.opentelemetry.io/proto/slim/otlp/collector/profiles/v1development v0.3.0/go.mod h1:I89cynRj8y+383o7tEQVg2SVA6SRgDVIouWPUVXjx0U= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0 h1:CQvJSldHRUN6Z8jsUeYv8J0lXRvygALXIzsmAeCcZE0= -go.opentelemetry.io/proto/slim/otlp/profiles/v1development v0.3.0/go.mod h1:xSQ+mEfJe/GjK1LXEyVOoSI1N9JV9ZI923X5kup43W4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= -go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/splunkinputsreceiver/config.go b/pkg/splunkinputsreceiver/config.go index 5edf5ff..b52e3ff 100644 --- a/pkg/splunkinputsreceiver/config.go +++ b/pkg/splunkinputsreceiver/config.go @@ -3,23 +3,15 @@ package splunkinputsreceiver -import "go.opentelemetry.io/collector/component" - type Config struct { // BaseDir is the Splunk installation root ($SPLUNK_HOME). inputs.conf, // transforms.conf, and props.conf are discovered across etc/apps/* using // standard Splunk precedence. Overrides $SPLUNK_HOME when set. - // Mutually exclusive with Path and WatchObservers. + // Mutually exclusive with Path. BaseDir string `mapstructure:"base_dir"` // Path is the absolute path to a single TA directory. When set, only that // directory's inputs.conf, transforms.conf, and props.conf are read with no - // btool-style layering. Mutually exclusive with BaseDir and WatchObservers. + // btool-style layering. Mutually exclusive with BaseDir. Path string `mapstructure:"path"` - - // WatchObservers is a list of observer extension IDs (e.g. folder_observer) - // to subscribe to. For each directory endpoint received, a splunk_inputs - // sub-receiver is started using that directory as Path. Mutually exclusive - // with BaseDir and Path. - WatchObservers []component.ID `mapstructure:"watch_observers"` } diff --git a/pkg/splunkinputsreceiver/factory_test.go b/pkg/splunkinputsreceiver/factory_test.go index 910f481..7e15bbf 100644 --- a/pkg/splunkinputsreceiver/factory_test.go +++ b/pkg/splunkinputsreceiver/factory_test.go @@ -14,6 +14,7 @@ import ( "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/pdata/plog" "go.opentelemetry.io/collector/receiver" + "go.uber.org/zap" "github.com/splunk/tarunner/pkg/splunkinputsreceiver" ) @@ -137,7 +138,8 @@ func (nopConsumer) ConsumeLogs(context.Context, plog.Logs) error { func newReceiverSettings() receiver.Settings { return receiver.Settings{ - ID: component.MustNewID("splunk_inputs"), + ID: component.MustNewID("splunk_inputs"), + TelemetrySettings: component.TelemetrySettings{Logger: zap.NewNop()}, } } diff --git a/pkg/splunkinputsreceiver/go.mod b/pkg/splunkinputsreceiver/go.mod index 081cc37..45d455b 100644 --- a/pkg/splunkinputsreceiver/go.mod +++ b/pkg/splunkinputsreceiver/go.mod @@ -3,7 +3,6 @@ module github.com/splunk/tarunner/pkg/splunkinputsreceiver go 1.26.4 require ( - github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 github.com/splunk/tarunner v0.5.1 github.com/stretchr/testify v1.11.1 go.opentelemetry.io/collector/component v1.63.0 diff --git a/pkg/splunkinputsreceiver/go.sum b/pkg/splunkinputsreceiver/go.sum index f36f098..63e632a 100644 --- a/pkg/splunkinputsreceiver/go.sum +++ b/pkg/splunkinputsreceiver/go.sum @@ -138,8 +138,6 @@ github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFd github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0 h1:65Y/2RuI7ov7PbwL4EE4Jf0J/2GIKGRN+lmmRU7sj7k= github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0/go.mod h1:EEHR9WkKjnZA30xrn7tz6jiczE49RVJnQOgLqS4nJDE= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0 h1:ZUHfd8qumKAMctr0CnRdsMjfke/RBU8702O3auyWMtM= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0/go.mod h1:x6bJlY9KOnHTbRqKhdmR5jbutjunFsDC5r+OP60+RRU= github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.157.0 h1:aQVvm4iiT1DUx2CzeDLEWETDKgbBvWAPJgancj6SQXY= diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index 799fc2f..abd7c9d 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -8,16 +8,9 @@ import ( "errors" "fmt" "os" - "sync" "go.opentelemetry.io/collector/component" - "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/receiver" - "go.uber.org/zap" - - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" - - "github.com/splunk/tarunner/internal/tabuilder" ) var nopInstance = &nopReceiver{} @@ -60,126 +53,6 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { } } -// watchingReceiver subscribes to one or more observers and starts a sub-receiver -// per directory endpoint. -type watchingReceiver struct { - cfg Config - settings receiver.Settings - logs consumer.Logs - opts factoryOptions - - mu sync.Mutex - active map[observer.EndpointID]receiver.Logs - host component.Host - ctx context.Context - cancel context.CancelFunc -} - -var _ observer.Notify = (*watchingReceiver)(nil) - -func (w *watchingReceiver) ID() observer.NotifyID { - return observer.NotifyID(w.settings.ID.String()) -} - -func (w *watchingReceiver) Start(ctx context.Context, host component.Host) error { - w.host = host - w.ctx, w.cancel = context.WithCancel(ctx) - - for _, obsID := range w.cfg.WatchObservers { - w.settings.Logger.Info("splunk_inputs: subscribing to observer", zap.String("observer", obsID.String())) - ext, ok := host.GetExtensions()[obsID] - if !ok { - return fmt.Errorf("splunk_inputs: observer extension %q not found", obsID) - } - obs, ok := ext.(observer.Observable) - if !ok { - return fmt.Errorf("splunk_inputs: extension %q does not implement observer.Observable", obsID) - } - obs.ListAndWatch(w) - } - return nil -} - -func (w *watchingReceiver) Shutdown(ctx context.Context) error { - if w.cancel != nil { - w.cancel() - } - w.mu.Lock() - defer w.mu.Unlock() - var errs []error - for _, r := range w.active { - errs = append(errs, r.Shutdown(ctx)) - } - return errors.Join(errs...) -} - -func (w *watchingReceiver) OnAdd(endpoints []observer.Endpoint) { - for _, e := range endpoints { - path, ok := e.Details.Env()["path"].(string) - if !ok || path == "" { - w.settings.Logger.Warn("splunk_inputs: endpoint missing path attribute", zap.String("id", string(e.ID))) - continue - } - w.settings.Logger.Info("splunk_inputs: building receivers for endpoint", zap.String("path", path)) - inputs, err := tabuilder.ReadInputs(path) - if err != nil { - w.settings.Logger.Error("splunk_inputs: failed to read inputs for endpoint", - zap.String("path", path), zap.Error(err)) - continue - } - transforms, err := tabuilder.ReadTransforms(path) - if err != nil { - w.settings.Logger.Error("splunk_inputs: failed to read transforms for endpoint", - zap.String("path", path), zap.Error(err)) - continue - } - props, err := tabuilder.ReadProps(path) - if err != nil { - w.settings.Logger.Error("splunk_inputs: failed to read props for endpoint", - zap.String("path", path), zap.Error(err)) - continue - } - receivers, err := w.opts.createReceivers(w.ctx, inputs, transforms, props, path, w.logs, w.settings) - if err != nil { - w.settings.Logger.Error("splunk_inputs: failed to create receivers for endpoint", - zap.String("path", path), zap.Error(err)) - continue - } - r := packReceivers(receivers) - if err := r.Start(w.ctx, w.host); err != nil { - w.settings.Logger.Error("splunk_inputs: failed to start receiver for endpoint", - zap.String("path", path), zap.Error(err)) - continue - } - w.settings.Logger.Info("splunk_inputs: started receiver for endpoint", zap.String("path", path)) - w.mu.Lock() - w.active[e.ID] = r - w.mu.Unlock() - } -} - -func (w *watchingReceiver) OnRemove(endpoints []observer.Endpoint) { - for _, e := range endpoints { - w.mu.Lock() - r, ok := w.active[e.ID] - if ok { - delete(w.active, e.ID) - } - w.mu.Unlock() - if ok { - if err := r.Shutdown(w.ctx); err != nil { - w.settings.Logger.Error("splunk_inputs: failed to shutdown receiver for removed endpoint", - zap.String("id", string(e.ID)), zap.Error(err)) - } - } - } -} - -func (w *watchingReceiver) OnChange(endpoints []observer.Endpoint) { - w.OnRemove(endpoints) - w.OnAdd(endpoints) -} - // resolveTADir returns the directory to read conf files from. // When Path is set, it is used directly (single-TA mode). // Otherwise base_dir or $SPLUNK_HOME is used for the full btool-style walk. diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index dbb1772..ad36d04 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -13,8 +13,6 @@ import ( "go.opentelemetry.io/collector/receiver" "go.uber.org/zap" - "github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer" - "github.com/splunk/tarunner/internal/conf" "github.com/splunk/tarunner/internal/stanza" "github.com/splunk/tarunner/internal/tabuilder" @@ -87,19 +85,6 @@ func newFactoryOptions(opts ...Option) factoryOptions { func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - if len(cfg.WatchObservers) > 0 { - if cfg.BaseDir != "" || cfg.Path != "" { - return nil, fmt.Errorf("splunk_inputs: watch_observers is mutually exclusive with base_dir and path") - } - return &watchingReceiver{ - cfg: cfg, - settings: settings, - logs: logs, - opts: o, - active: make(map[observer.EndpointID]receiver.Logs), - }, nil - } - taDir, err := resolveTADir(cfg) if err != nil { return nil, err @@ -162,8 +147,7 @@ func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next } l, err := tabuilder.CreateReceiver(ctx, baseDir, next, input, transforms, props, settings.TelemetrySettings) if l == nil && err == nil { - settings.Logger.Info("splunk_inputs: skipping unsupported stanza kind", zap.String("stanza", input.Configuration.Stanza.Name)) - return nopInstance, nil + return nil, fmt.Errorf("unsupported scheme %q", scheme) } return l, err } From 37f777cfb237b219c621cd2757401afb21fbf48e Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:41:42 +0200 Subject: [PATCH 27/46] delete baseDir parameter --- internal/collector/collector.go | 7 +-- internal/tabuilder/tabuilder.go | 71 +++++++++++++++++++------ internal/tabuilder/tabuilder_test.go | 2 +- pkg/splunkinputsreceiver/config.go | 12 ++--- pkg/splunkinputsreceiver/receiver.go | 25 ++++----- pkg/splunkinputsreceiver/subreceiver.go | 19 ++++--- 6 files changed, 83 insertions(+), 53 deletions(-) diff --git a/internal/collector/collector.go b/internal/collector/collector.go index 07b4ba4..5ef0dc7 100644 --- a/internal/collector/collector.go +++ b/internal/collector/collector.go @@ -36,15 +36,16 @@ func Run(baseDir string, cfg *config.Config) (func(), error) { if err != nil { return nil, err } - inputs, err := tabuilder.ReadInputs(baseDir) + dirs := tabuilder.ConfDirs(baseDir) + inputs, err := tabuilder.ReadInputs(dirs) if err != nil { return nil, err } - transforms, err := tabuilder.ReadTransforms(baseDir) + transforms, err := tabuilder.ReadTransforms(dirs) if err != nil { return nil, err } - props, err := tabuilder.ReadProps(baseDir) + props, err := tabuilder.ReadProps(dirs) if err != nil { return nil, err } diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index cbfadfa..98565fd 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -162,9 +162,22 @@ func taDirs(taDir string) []string { } } -// isSingleTA returns true when the path looks like a single TA directory +// taDirsWithSystem returns the conf search path for a single TA directory merged +// with system-level config from splunkHome, following Splunk precedence: +// system default → TA default → TA local → system local. +func taDirsWithSystem(splunkHome, taDir string) []string { + etcDir := filepath.Join(splunkHome, "etc") + return []string{ + filepath.Join(etcDir, "system", "default"), + filepath.Join(taDir, "default"), + filepath.Join(taDir, "local"), + filepath.Join(etcDir, "system", "local"), + } +} + +// IsSingleTA returns true when the path looks like a single TA directory // (has a default/ or local/ subdirectory but no etc/apps/ layout). -func isSingleTA(path string) bool { +func IsSingleTA(path string) bool { _, errEtc := os.Stat(filepath.Join(path, "etc", "apps")) _, errDefault := os.Stat(filepath.Join(path, "default")) _, errLocal := os.Stat(filepath.Join(path, "local")) @@ -174,12 +187,22 @@ func isSingleTA(path string) bool { } func confDirs(path string) []string { - if isSingleTA(path) { + if IsSingleTA(path) { return taDirs(path) } return splunkHomeDirs(path) } +// confDirsWithSystem returns the conf search path for a single TA directory +// combined with system-level config. If splunkHome is empty, falls back to +// taDirs (no system config). +func confDirsWithSystem(splunkHome, taDir string) []string { + if splunkHome == "" { + return taDirs(taDir) + } + return taDirsWithSystem(splunkHome, taDir) +} + func readConfFiles(paths []string) ([][]byte, error) { var payloads [][]byte for _, path := range paths { @@ -195,12 +218,26 @@ func readConfFiles(paths []string) ([][]byte, error) { return payloads, nil } -// ReadInputs discovers and merges inputs.conf files from dir using standard -// Splunk precedence. dir may be a Splunk home or a single TA directory. -// Returns nil (no error) when absent. -func ReadInputs(dir string) ([]conf.Input, error) { +// ConfDirs returns the conf search path for dir, auto-detecting whether it is +// a single TA directory or a full Splunk home. +func ConfDirs(dir string) []string { + return confDirs(dir) +} + +// ConfDirsWithSystem returns the conf search path for a single TA directory +// combined with system-level config from splunkHome, following Splunk +// precedence: system default → TA default → TA local → system local. +// If splunkHome is empty, only the TA's own default/local are included. +func ConfDirsWithSystem(splunkHome, taDir string) []string { + return confDirsWithSystem(splunkHome, taDir) +} + +// ReadInputs discovers and merges inputs.conf files from the given search +// directories. Use ConfDirs or ConfDirsWithSystem to build the dirs slice. +// Returns nil (no error) when no inputs.conf files are found. +func ReadInputs(dirs []string) ([]conf.Input, error) { var layers [][]conf.Input - for _, path := range confFilePaths(confDirs(dir), "inputs.conf") { + for _, path := range confFilePaths(dirs, "inputs.conf") { b, err := os.ReadFile(path) if errors.Is(err, os.ErrNotExist) { continue @@ -218,10 +255,10 @@ func ReadInputs(dir string) ([]conf.Input, error) { return conf.MergeInputs(layers), nil } -// ReadTransforms discovers and merges transforms.conf files from dir using -// standard Splunk precedence. Returns nil (no error) when absent. -func ReadTransforms(dir string) ([]conf.Transform, error) { - payloads, err := readConfFiles(confFilePaths(confDirs(dir), "transforms.conf")) +// ReadTransforms discovers and merges transforms.conf files from the given +// search directories. Returns nil (no error) when absent. +func ReadTransforms(dirs []string) ([]conf.Transform, error) { + payloads, err := readConfFiles(confFilePaths(dirs, "transforms.conf")) if err != nil { return nil, err } @@ -236,10 +273,10 @@ func ReadTransforms(dir string) ([]conf.Transform, error) { return conf.MergeTransforms(layers), nil } -// ReadProps discovers and merges props.conf files from dir using standard -// Splunk precedence. Returns nil (no error) when absent. -func ReadProps(dir string) ([]conf.Prop, error) { - payloads, err := readConfFiles(confFilePaths(confDirs(dir), "props.conf")) +// ReadProps discovers and merges props.conf files from the given search +// directories. Returns nil (no error) when absent. +func ReadProps(dirs []string) ([]conf.Prop, error) { + payloads, err := readConfFiles(confFilePaths(dirs, "props.conf")) if err != nil { return nil, err } @@ -257,7 +294,7 @@ func ReadProps(dir string) ([]conf.Prop, error) { // ReadOutputs merges outputs.conf across $SPLUNK_HOME using standard Splunk // precedence. Use HTTPOut (or future TCPOut, etc.) to extract a specific type. func ReadOutputs(splunkHome string) (conf.ConfMap, error) { - payloads, err := readConfFiles(confFilePaths(confDirs(splunkHome), "outputs.conf")) + payloads, err := readConfFiles(confFilePaths(ConfDirs(splunkHome), "outputs.conf")) if err != nil { return nil, err } diff --git a/internal/tabuilder/tabuilder_test.go b/internal/tabuilder/tabuilder_test.go index a8599a0..81bb8d2 100644 --- a/internal/tabuilder/tabuilder_test.go +++ b/internal/tabuilder/tabuilder_test.go @@ -102,7 +102,7 @@ func TestReadTransforms(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - transforms, err := ReadTransforms(test.splunkHome) + transforms, err := ReadTransforms(ConfDirs(test.splunkHome)) require.NoError(t, err) require.Len(t, transforms, 1) require.Equal(t, test.expectedName, transforms[0].Name) diff --git a/pkg/splunkinputsreceiver/config.go b/pkg/splunkinputsreceiver/config.go index b52e3ff..939e9fb 100644 --- a/pkg/splunkinputsreceiver/config.go +++ b/pkg/splunkinputsreceiver/config.go @@ -4,14 +4,8 @@ package splunkinputsreceiver type Config struct { - // BaseDir is the Splunk installation root ($SPLUNK_HOME). inputs.conf, - // transforms.conf, and props.conf are discovered across etc/apps/* using - // standard Splunk precedence. Overrides $SPLUNK_HOME when set. - // Mutually exclusive with Path. - BaseDir string `mapstructure:"base_dir"` - - // Path is the absolute path to a single TA directory. When set, only that - // directory's inputs.conf, transforms.conf, and props.conf are read with no - // btool-style layering. Mutually exclusive with BaseDir. + // Path is the absolute path to a single TA directory + // (e.g. /opt/splunk/etc/apps/Splunk_TA_nix). + // The Splunk home is derived automatically by walking up the directory tree. Path string `mapstructure:"path"` } diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index abd7c9d..d946ddb 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -6,8 +6,8 @@ package splunkinputsreceiver import ( "context" "errors" - "fmt" "os" + "path/filepath" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/receiver" @@ -53,19 +53,14 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { } } -// resolveTADir returns the directory to read conf files from. -// When Path is set, it is used directly (single-TA mode). -// Otherwise base_dir or $SPLUNK_HOME is used for the full btool-style walk. -func resolveTADir(cfg Config) (string, error) { - if cfg.Path != "" { - return cfg.Path, nil +// resolveSplunkHome derives the Splunk installation root from a TA path. +// For a standard install the TA sits at $SPLUNK_HOME/etc/apps/, so the +// home is three levels up. If the derived path does not exist, $SPLUNK_HOME +// is used as a fallback. +func resolveSplunkHome(taPath string) string { + derived := filepath.Dir(filepath.Dir(filepath.Dir(taPath))) + if _, err := os.Stat(derived); err == nil { + return derived } - splunkHome := cfg.BaseDir - if splunkHome == "" { - splunkHome = os.Getenv("SPLUNK_HOME") - } - if splunkHome == "" { - return "", fmt.Errorf("splunk_inputs: path is not set, base_dir is not set, and SPLUNK_HOME is not defined") - } - return splunkHome, nil + return os.Getenv("SPLUNK_HOME") } diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index ad36d04..1b6ca01 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -85,25 +85,28 @@ func newFactoryOptions(opts ...Option) factoryOptions { func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - taDir, err := resolveTADir(cfg) - if err != nil { - return nil, err + if cfg.Path == "" { + return nil, fmt.Errorf("splunk_inputs: path is required") } - - inputs, err := tabuilder.ReadInputs(taDir) + dirs := tabuilder.ConfDirs(cfg.Path) + if tabuilder.IsSingleTA(cfg.Path) { + splunkHome := resolveSplunkHome(cfg.Path) + dirs = tabuilder.ConfDirsWithSystem(splunkHome, cfg.Path) + } + inputs, err := tabuilder.ReadInputs(dirs) if err != nil { return nil, err } - transforms, err := tabuilder.ReadTransforms(taDir) + transforms, err := tabuilder.ReadTransforms(dirs) if err != nil { return nil, err } - props, err := tabuilder.ReadProps(taDir) + props, err := tabuilder.ReadProps(dirs) if err != nil { return nil, err } - receivers, err := o.createReceivers(ctx, inputs, transforms, props, taDir, logs, settings) + receivers, err := o.createReceivers(ctx, inputs, transforms, props, cfg.Path, logs, settings) if err != nil { return nil, err } From 4f7791db782ea3f0b8744ce387078813f4cf54cb Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Mon, 24 Aug 2026 13:42:52 +0200 Subject: [PATCH 28/46] fix tests --- pkg/splunkinputsreceiver/factory_test.go | 34 ++++++++++++------------ 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/pkg/splunkinputsreceiver/factory_test.go b/pkg/splunkinputsreceiver/factory_test.go index 7e15bbf..630354c 100644 --- a/pkg/splunkinputsreceiver/factory_test.go +++ b/pkg/splunkinputsreceiver/factory_test.go @@ -20,25 +20,25 @@ import ( ) func TestWithSubReceiverRegistersCustomScheme(t *testing.T) { - baseDir := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") + path := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: baseDir}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) - require.Equal(t, baseDir, fake.request.BaseDir) + require.Equal(t, path, fake.request.BaseDir) require.Equal(t, "/thing", fake.request.Path) require.Equal(t, "custom:///thing", fake.request.Input.Configuration.Stanza.Name) } func TestWithSubReceiverOverridesBuiltInAndHandlesEmptySchemeAsScript(t *testing.T) { - baseDir := writeTA(t, "[modinput]\ninterval = -1\n") + path := writeTA(t, "[modinput]\ninterval = -1\n") fake := &fakeSubReceiverFactory{scheme: "script"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: baseDir}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) @@ -47,11 +47,11 @@ func TestWithSubReceiverOverridesBuiltInAndHandlesEmptySchemeAsScript(t *testing } func TestWithSubReceiverMatchesNormalizedScheme(t *testing.T) { - baseDir := writeTA(t, "[Custom://thing]\n") + path := writeTA(t, "[Custom://thing]\n") fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: baseDir}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) @@ -60,34 +60,34 @@ func TestWithSubReceiverMatchesNormalizedScheme(t *testing.T) { } func TestWithSubReceiverRejectsUnsupportedScheme(t *testing.T) { - baseDir := writeTA(t, "[unsupported://thing]\n") + path := writeTA(t, "[unsupported://thing]\n") factory := splunkinputsreceiver.NewFactory() - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: baseDir}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) require.ErrorContains(t, err, `unsupported scheme "unsupported"`) require.Nil(t, rcvr) } func TestWithSubReceiverSkipsDisabledCustomStanza(t *testing.T) { - baseDir := writeTA(t, "[custom:///thing]\ndisabled = 1\n") + path := writeTA(t, "[custom:///thing]\ndisabled = 1\n") fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: baseDir}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.False(t, fake.called) } func TestWithSubReceiverRequestIncludesPropsAndTransforms(t *testing.T) { - baseDir := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") - defaultDir := filepath.Join(baseDir, "default") + path := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") + defaultDir := filepath.Join(path, "default") require.NoError(t, os.WriteFile(filepath.Join(defaultDir, "props.conf"), []byte("[custom]\nTRANSFORMS-routing = route\n"), 0o600)) require.NoError(t, os.WriteFile(filepath.Join(defaultDir, "transforms.conf"), []byte("[route]\nREGEX = ^(.*)$\nFORMAT = $1\n"), 0o600)) fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: baseDir}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) @@ -145,9 +145,9 @@ func newReceiverSettings() receiver.Settings { func writeTA(t *testing.T, inputsConf string) string { t.Helper() - baseDir := t.TempDir() - defaultDir := filepath.Join(baseDir, "default") + path := t.TempDir() + defaultDir := filepath.Join(path, "default") require.NoError(t, os.Mkdir(defaultDir, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(defaultDir, "inputs.conf"), []byte(inputsConf), 0o600)) - return baseDir + return path } From 352e67cc7ceecd7a0e56fb42bd229ce2a0f24a67 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:14:35 +0200 Subject: [PATCH 29/46] bring back baseDir version --- pkg/splunkinputsreceiver/config.go | 9 +++-- pkg/splunkinputsreceiver/factory_test.go | 47 ++++++++++++---------- pkg/splunkinputsreceiver/receiver.go | 20 +++++----- pkg/splunkinputsreceiver/subreceiver.go | 51 ++++++++++++++++-------- 4 files changed, 75 insertions(+), 52 deletions(-) diff --git a/pkg/splunkinputsreceiver/config.go b/pkg/splunkinputsreceiver/config.go index 939e9fb..9d9b9cb 100644 --- a/pkg/splunkinputsreceiver/config.go +++ b/pkg/splunkinputsreceiver/config.go @@ -4,8 +4,9 @@ package splunkinputsreceiver type Config struct { - // Path is the absolute path to a single TA directory - // (e.g. /opt/splunk/etc/apps/Splunk_TA_nix). - // The Splunk home is derived automatically by walking up the directory tree. - Path string `mapstructure:"path"` + // BaseDir is the Splunk installation root ($SPLUNK_HOME). TAs are + // discovered under etc/apps/* and conf files are layered using standard + // Splunk precedence. Falls back to the $SPLUNK_HOME environment variable + // when not set. + BaseDir string `mapstructure:"base_dir"` } diff --git a/pkg/splunkinputsreceiver/factory_test.go b/pkg/splunkinputsreceiver/factory_test.go index 630354c..717f81a 100644 --- a/pkg/splunkinputsreceiver/factory_test.go +++ b/pkg/splunkinputsreceiver/factory_test.go @@ -20,74 +20,75 @@ import ( ) func TestWithSubReceiverRegistersCustomScheme(t *testing.T) { - path := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") + splunkHome := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: splunkHome}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) - require.Equal(t, path, fake.request.BaseDir) + require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "ta"), fake.request.BaseDir) require.Equal(t, "/thing", fake.request.Path) require.Equal(t, "custom:///thing", fake.request.Input.Configuration.Stanza.Name) } func TestWithSubReceiverOverridesBuiltInAndHandlesEmptySchemeAsScript(t *testing.T) { - path := writeTA(t, "[modinput]\ninterval = -1\n") + splunkHome := writeTA(t, "[modinput]\ninterval = -1\n") fake := &fakeSubReceiverFactory{scheme: "script"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: splunkHome}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) - require.Equal(t, "modinput", fake.request.Path) + require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "ta"), fake.request.BaseDir) require.Equal(t, "modinput", fake.request.Input.Configuration.Stanza.Name) } func TestWithSubReceiverMatchesNormalizedScheme(t *testing.T) { - path := writeTA(t, "[Custom://thing]\n") + splunkHome := writeTA(t, "[Custom://thing]\n") fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: splunkHome}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) + require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "ta"), fake.request.BaseDir) require.Equal(t, "thing", fake.request.Path) require.Equal(t, "Custom://thing", fake.request.Input.Configuration.Stanza.Name) } func TestWithSubReceiverRejectsUnsupportedScheme(t *testing.T) { - path := writeTA(t, "[unsupported://thing]\n") + splunkHome := writeTA(t, "[unsupported://thing]\n") factory := splunkinputsreceiver.NewFactory() - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: splunkHome}, nopConsumer{}) require.ErrorContains(t, err, `unsupported scheme "unsupported"`) require.Nil(t, rcvr) } func TestWithSubReceiverSkipsDisabledCustomStanza(t *testing.T) { - path := writeTA(t, "[custom:///thing]\ndisabled = 1\n") + splunkHome := writeTA(t, "[custom:///thing]\ndisabled = 1\n") fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: splunkHome}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.False(t, fake.called) } func TestWithSubReceiverRequestIncludesPropsAndTransforms(t *testing.T) { - path := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") - defaultDir := filepath.Join(path, "default") - require.NoError(t, os.WriteFile(filepath.Join(defaultDir, "props.conf"), []byte("[custom]\nTRANSFORMS-routing = route\n"), 0o600)) - require.NoError(t, os.WriteFile(filepath.Join(defaultDir, "transforms.conf"), []byte("[route]\nREGEX = ^(.*)$\nFORMAT = $1\n"), 0o600)) + splunkHome := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") + taDefaultDir := filepath.Join(splunkHome, "etc", "apps", "ta", "default") + require.NoError(t, os.WriteFile(filepath.Join(taDefaultDir, "props.conf"), []byte("[custom]\nTRANSFORMS-routing = route\n"), 0o600)) + require.NoError(t, os.WriteFile(filepath.Join(taDefaultDir, "transforms.conf"), []byte("[route]\nREGEX = ^(.*)$\nFORMAT = $1\n"), 0o600)) fake := &fakeSubReceiverFactory{scheme: "custom"} factory := splunkinputsreceiver.NewFactory(splunkinputsreceiver.WithSubReceiver(fake)) - rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{Path: path}, nopConsumer{}) + rcvr, err := factory.CreateLogs(context.Background(), newReceiverSettings(), splunkinputsreceiver.Config{BaseDir: splunkHome}, nopConsumer{}) require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) @@ -143,11 +144,13 @@ func newReceiverSettings() receiver.Settings { } } +// writeTA creates a minimal Splunk home layout with one TA and returns the +// splunk home path. The TA is placed at /etc/apps/ta/. func writeTA(t *testing.T, inputsConf string) string { t.Helper() - path := t.TempDir() - defaultDir := filepath.Join(path, "default") - require.NoError(t, os.Mkdir(defaultDir, 0o755)) - require.NoError(t, os.WriteFile(filepath.Join(defaultDir, "inputs.conf"), []byte(inputsConf), 0o600)) - return path + splunkHome := t.TempDir() + taDefaultDir := filepath.Join(splunkHome, "etc", "apps", "ta", "default") + require.NoError(t, os.MkdirAll(taDefaultDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(taDefaultDir, "inputs.conf"), []byte(inputsConf), 0o600)) + return splunkHome } diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index d946ddb..f7b1d73 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -6,8 +6,8 @@ package splunkinputsreceiver import ( "context" "errors" + "fmt" "os" - "path/filepath" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/receiver" @@ -53,14 +53,14 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { } } -// resolveSplunkHome derives the Splunk installation root from a TA path. -// For a standard install the TA sits at $SPLUNK_HOME/etc/apps/, so the -// home is three levels up. If the derived path does not exist, $SPLUNK_HOME -// is used as a fallback. -func resolveSplunkHome(taPath string) string { - derived := filepath.Dir(filepath.Dir(filepath.Dir(taPath))) - if _, err := os.Stat(derived); err == nil { - return derived +// resolveSplunkHome returns the Splunk installation root from the config, +// falling back to the $SPLUNK_HOME environment variable. +func resolveSplunkHome(cfg Config) (string, error) { + if cfg.BaseDir != "" { + return cfg.BaseDir, nil } - return os.Getenv("SPLUNK_HOME") + if home := os.Getenv("SPLUNK_HOME"); home != "" { + return home, nil + } + return "", fmt.Errorf("splunk_inputs: base_dir is not set and $SPLUNK_HOME is not defined") } diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 1b6ca01..72f76b3 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -6,6 +6,8 @@ package splunkinputsreceiver import ( "context" "fmt" + "path/filepath" + "sort" "strings" "go.opentelemetry.io/collector/component" @@ -85,32 +87,49 @@ func newFactoryOptions(opts ...Option) factoryOptions { func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - if cfg.Path == "" { - return nil, fmt.Errorf("splunk_inputs: path is required") - } - dirs := tabuilder.ConfDirs(cfg.Path) - if tabuilder.IsSingleTA(cfg.Path) { - splunkHome := resolveSplunkHome(cfg.Path) - dirs = tabuilder.ConfDirsWithSystem(splunkHome, cfg.Path) - } - inputs, err := tabuilder.ReadInputs(dirs) + splunkHome, err := resolveSplunkHome(cfg) if err != nil { return nil, err } - transforms, err := tabuilder.ReadTransforms(dirs) + + taDirs, err := discoverTAs(splunkHome) if err != nil { return nil, err } - props, err := tabuilder.ReadProps(dirs) - if err != nil { - return nil, err + + var allReceivers []receiver.Logs + for _, taDir := range taDirs { + dirs := tabuilder.ConfDirsWithSystem(splunkHome, taDir) + inputs, err := tabuilder.ReadInputs(dirs) + if err != nil { + return nil, err + } + transforms, err := tabuilder.ReadTransforms(dirs) + if err != nil { + return nil, err + } + props, err := tabuilder.ReadProps(dirs) + if err != nil { + return nil, err + } + receivers, err := o.createReceivers(ctx, inputs, transforms, props, taDir, logs, settings) + if err != nil { + return nil, err + } + allReceivers = append(allReceivers, receivers...) } + return packReceivers(allReceivers), nil +} - receivers, err := o.createReceivers(ctx, inputs, transforms, props, cfg.Path, logs, settings) +// discoverTAs returns all direct child directories under $SPLUNK_HOME/etc/apps. +func discoverTAs(splunkHome string) ([]string, error) { + appsDir := filepath.Join(splunkHome, "etc", "apps") + entries, err := filepath.Glob(filepath.Join(appsDir, "*")) if err != nil { - return nil, err + return nil, fmt.Errorf("splunk_inputs: failed to scan %s: %w", appsDir, err) } - return packReceivers(receivers), nil + sort.Strings(entries) + return entries, nil } func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { From 049a2067620b0a1cb9369197d6244424fb2d75bf Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Tue, 25 Aug 2026 13:55:06 +0200 Subject: [PATCH 30/46] fix merge inputs --- internal/conf/inputs.go | 27 +++++++++++++++++++++++++-- internal/conf/inputs_test.go | 16 ++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index 8da6253..d3d356e 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -98,7 +98,8 @@ func ReadInput(payload []byte, appDir string) ([]Input, error) { // MergeInputs merges multiple slices of inputs, with later slices taking // precedence over earlier ones (local/ wins over default/). -// Stanzas are keyed by name; the last definition of a stanza wins entirely. +// Stanzas are keyed by name; params are merged key by key so that a later +// layer only overrides the keys it explicitly sets. func MergeInputs(layers [][]Input) []Input { seen := make(map[string]int) var result []Input @@ -106,7 +107,7 @@ func MergeInputs(layers [][]Input) []Input { for _, input := range layer { name := input.Configuration.Stanza.Name if idx, ok := seen[name]; ok { - result[idx] = input + result[idx] = mergeInput(result[idx], input) } else { seen[name] = len(result) result = append(result, input) @@ -116,6 +117,28 @@ func MergeInputs(layers [][]Input) []Input { return result } +// mergeInput merges override into base at the param level. +// Keys present in override replace the same key in base; keys only in base +// are preserved. +func mergeInput(base, override Input) Input { + merged := base + params := make(map[string]int, len(base.Configuration.Stanza.Params)) + mergedParams := append([]Param{}, base.Configuration.Stanza.Params...) + for i, p := range mergedParams { + params[p.Name] = i + } + for _, p := range override.Configuration.Stanza.Params { + if idx, ok := params[p.Name]; ok { + mergedParams[idx] = p + } else { + params[p.Name] = len(mergedParams) + mergedParams = append(mergedParams, p) + } + } + merged.Configuration.Stanza.Params = mergedParams + return merged +} + func (i *Input) ToXML() ([]byte, error) { b, err := xml.MarshalIndent(i, "", " ") return append([]byte(xmlDeclaration), b...), err diff --git a/internal/conf/inputs_test.go b/internal/conf/inputs_test.go index 38048db..a8224cf 100644 --- a/internal/conf/inputs_test.go +++ b/internal/conf/inputs_test.go @@ -112,6 +112,22 @@ func TestTwoInputs(t *testing.T) { }}, res) } +func TestMergeInputsPartialOverride(t *testing.T) { + base := []Input{{Configuration: Configuration{Stanza: Stanza{ + Name: "monitor:///var/log/syslog", + Params: Params{{Name: "sourcetype", Value: "syslog"}, {Name: "index", Value: "main"}}, + }}}} + override := []Input{{Configuration: Configuration{Stanza: Stanza{ + Name: "monitor:///var/log/syslog", + Params: Params{{Name: "index", Value: "override"}}, + }}}} + + merged := MergeInputs([][]Input{base, override}) + require.Len(t, merged, 1) + assert.Equal(t, "syslog", merged[0].Configuration.Stanza.Params.Get("sourcetype").Value) + assert.Equal(t, "override", merged[0].Configuration.Stanza.Params.Get("index").Value) +} + func TestToXML(t *testing.T) { testStr := ` From 4a5c5c81caefd2293487279c0c2b527e7ee65e96 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:39:35 +0200 Subject: [PATCH 31/46] delete unnecessary functions --- internal/tabuilder/tabuilder.go | 47 ++++----------------------------- 1 file changed, 5 insertions(+), 42 deletions(-) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 98565fd..d3a1526 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -154,14 +154,6 @@ func splunkHomeDirs(splunkHome string) []string { return dirs } -// taDirs returns the conf search path for a single TA directory (default/ then local/). -func taDirs(taDir string) []string { - return []string{ - filepath.Join(taDir, "default"), - filepath.Join(taDir, "local"), - } -} - // taDirsWithSystem returns the conf search path for a single TA directory merged // with system-level config from splunkHome, following Splunk precedence: // system default → TA default → TA local → system local. @@ -175,34 +167,6 @@ func taDirsWithSystem(splunkHome, taDir string) []string { } } -// IsSingleTA returns true when the path looks like a single TA directory -// (has a default/ or local/ subdirectory but no etc/apps/ layout). -func IsSingleTA(path string) bool { - _, errEtc := os.Stat(filepath.Join(path, "etc", "apps")) - _, errDefault := os.Stat(filepath.Join(path, "default")) - _, errLocal := os.Stat(filepath.Join(path, "local")) - hasEtcApps := errEtc == nil - hasTA := errDefault == nil || errLocal == nil - return hasTA && !hasEtcApps -} - -func confDirs(path string) []string { - if IsSingleTA(path) { - return taDirs(path) - } - return splunkHomeDirs(path) -} - -// confDirsWithSystem returns the conf search path for a single TA directory -// combined with system-level config. If splunkHome is empty, falls back to -// taDirs (no system config). -func confDirsWithSystem(splunkHome, taDir string) []string { - if splunkHome == "" { - return taDirs(taDir) - } - return taDirsWithSystem(splunkHome, taDir) -} - func readConfFiles(paths []string) ([][]byte, error) { var payloads [][]byte for _, path := range paths { @@ -218,18 +182,17 @@ func readConfFiles(paths []string) ([][]byte, error) { return payloads, nil } -// ConfDirs returns the conf search path for dir, auto-detecting whether it is -// a single TA directory or a full Splunk home. -func ConfDirs(dir string) []string { - return confDirs(dir) +// ConfDirs returns the conf search path for a Splunk home directory, following +// standard Splunk precedence across all apps. +func ConfDirs(splunkHome string) []string { + return splunkHomeDirs(splunkHome) } // ConfDirsWithSystem returns the conf search path for a single TA directory // combined with system-level config from splunkHome, following Splunk // precedence: system default → TA default → TA local → system local. -// If splunkHome is empty, only the TA's own default/local are included. func ConfDirsWithSystem(splunkHome, taDir string) []string { - return confDirsWithSystem(splunkHome, taDir) + return taDirsWithSystem(splunkHome, taDir) } // ReadInputs discovers and merges inputs.conf files from the given search From 993b40128b6239b8dc4e16e6eb62f0bf15e3b8b6 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:09:18 +0200 Subject: [PATCH 32/46] fix script intervals --- internal/scriptedinput/input.go | 6 +++--- pkg/splunkinputsreceiver/factory_test.go | 10 +++++----- pkg/splunkinputsreceiver/subreceiver.go | 23 ++++++++++++++++++----- 3 files changed, 26 insertions(+), 13 deletions(-) diff --git a/internal/scriptedinput/input.go b/internal/scriptedinput/input.go index 7a12524..c118a3e 100644 --- a/internal/scriptedinput/input.go +++ b/internal/scriptedinput/input.go @@ -68,11 +68,11 @@ func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) if input.Configuration.Stanza.IsDisabled() { return false, nil } - intervalS := 3600 + intervalS := 3600.0 for _, p := range input.Configuration.Stanza.Params { if p.Name == "interval" { var err error - intervalS, err = strconv.Atoi(p.Value) + intervalS, err = strconv.ParseFloat(p.Value, 64) if err != nil { return false, err } @@ -95,7 +95,7 @@ func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) } }() } else { - interval := time.Duration(intervalS) * time.Second + interval := time.Duration(intervalS * float64(time.Second)) go func() { if !si.execute(baseDir, input) { return diff --git a/pkg/splunkinputsreceiver/factory_test.go b/pkg/splunkinputsreceiver/factory_test.go index 717f81a..dc9e01c 100644 --- a/pkg/splunkinputsreceiver/factory_test.go +++ b/pkg/splunkinputsreceiver/factory_test.go @@ -28,7 +28,7 @@ func TestWithSubReceiverRegistersCustomScheme(t *testing.T) { require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) - require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "ta"), fake.request.BaseDir) + require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "Splunk_TA_test"), fake.request.BaseDir) require.Equal(t, "/thing", fake.request.Path) require.Equal(t, "custom:///thing", fake.request.Input.Configuration.Stanza.Name) } @@ -42,7 +42,7 @@ func TestWithSubReceiverOverridesBuiltInAndHandlesEmptySchemeAsScript(t *testing require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) - require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "ta"), fake.request.BaseDir) + require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "Splunk_TA_test"), fake.request.BaseDir) require.Equal(t, "modinput", fake.request.Input.Configuration.Stanza.Name) } @@ -55,7 +55,7 @@ func TestWithSubReceiverMatchesNormalizedScheme(t *testing.T) { require.NoError(t, err) require.NotNil(t, rcvr) require.True(t, fake.called) - require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "ta"), fake.request.BaseDir) + require.Equal(t, filepath.Join(splunkHome, "etc", "apps", "Splunk_TA_test"), fake.request.BaseDir) require.Equal(t, "thing", fake.request.Path) require.Equal(t, "Custom://thing", fake.request.Input.Configuration.Stanza.Name) } @@ -82,7 +82,7 @@ func TestWithSubReceiverSkipsDisabledCustomStanza(t *testing.T) { func TestWithSubReceiverRequestIncludesPropsAndTransforms(t *testing.T) { splunkHome := writeTA(t, "[custom:///thing]\nsourcetype = custom\n") - taDefaultDir := filepath.Join(splunkHome, "etc", "apps", "ta", "default") + taDefaultDir := filepath.Join(splunkHome, "etc", "apps", "Splunk_TA_test", "default") require.NoError(t, os.WriteFile(filepath.Join(taDefaultDir, "props.conf"), []byte("[custom]\nTRANSFORMS-routing = route\n"), 0o600)) require.NoError(t, os.WriteFile(filepath.Join(taDefaultDir, "transforms.conf"), []byte("[route]\nREGEX = ^(.*)$\nFORMAT = $1\n"), 0o600)) fake := &fakeSubReceiverFactory{scheme: "custom"} @@ -149,7 +149,7 @@ func newReceiverSettings() receiver.Settings { func writeTA(t *testing.T, inputsConf string) string { t.Helper() splunkHome := t.TempDir() - taDefaultDir := filepath.Join(splunkHome, "etc", "apps", "ta", "default") + taDefaultDir := filepath.Join(splunkHome, "etc", "apps", "Splunk_TA_test", "default") require.NoError(t, os.MkdirAll(taDefaultDir, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(taDefaultDir, "inputs.conf"), []byte(inputsConf), 0o600)) return splunkHome diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 72f76b3..72fd252 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -5,9 +5,10 @@ package splunkinputsreceiver import ( "context" + "errors" "fmt" + "os" "path/filepath" - "sort" "strings" "go.opentelemetry.io/collector/component" @@ -121,15 +122,27 @@ func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Se return packReceivers(allReceivers), nil } -// discoverTAs returns all direct child directories under $SPLUNK_HOME/etc/apps. +// discoverTAs returns all direct child directories under $SPLUNK_HOME/etc/apps +// whose name starts with "splunk_ta_" (case-insensitive). func discoverTAs(splunkHome string) ([]string, error) { appsDir := filepath.Join(splunkHome, "etc", "apps") - entries, err := filepath.Glob(filepath.Join(appsDir, "*")) + entries, err := os.ReadDir(appsDir) if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } return nil, fmt.Errorf("splunk_inputs: failed to scan %s: %w", appsDir, err) } - sort.Strings(entries) - return entries, nil + var taDirs []string + for _, entry := range entries { + if !entry.IsDir() { + continue + } + if strings.HasPrefix(strings.ToLower(entry.Name()), "splunk_ta_") { + taDirs = append(taDirs, filepath.Join(appsDir, entry.Name())) + } + } + return taDirs, nil } func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { From cb11c5af99bdee61e11d37a7a16a1e643e7705a5 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:32:21 +0200 Subject: [PATCH 33/46] skip unkonwn stanzas --- pkg/splunkinputsreceiver/subreceiver.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 72fd252..3dd4501 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -157,6 +157,9 @@ func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, tra if err != nil { return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) } + if l == nil { + continue + } receivers = append(receivers, l) } return receivers, nil @@ -167,6 +170,11 @@ func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next if err != nil { return nil, err } + if parsed.Target == "" { + // stanza has no target (e.g. [script], [tcp]) — it is a default-settings + // stanza, not an actual input to run. + return nil, nil + } scheme := parsed.Kind if scheme == "" { scheme = "script" From 53ac7eb06eec1b134223541f687db4f368965314 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:50:01 +0200 Subject: [PATCH 34/46] bring back unknown stanza check --- pkg/splunkinputsreceiver/subreceiver.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 3dd4501..72fd252 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -157,9 +157,6 @@ func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, tra if err != nil { return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) } - if l == nil { - continue - } receivers = append(receivers, l) } return receivers, nil @@ -170,11 +167,6 @@ func (o factoryOptions) createReceiver(ctx context.Context, baseDir string, next if err != nil { return nil, err } - if parsed.Target == "" { - // stanza has no target (e.g. [script], [tcp]) — it is a default-settings - // stanza, not an actual input to run. - return nil, nil - } scheme := parsed.Kind if scheme == "" { scheme = "script" From 17dde9070c87f3f6b70e37eebe0b574a337e5aa3 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 09:53:43 +0200 Subject: [PATCH 35/46] bring back script float and script issue --- internal/scriptedinput/input.go | 52 ++++++++------------------------- 1 file changed, 12 insertions(+), 40 deletions(-) diff --git a/internal/scriptedinput/input.go b/internal/scriptedinput/input.go index c118a3e..1320464 100644 --- a/internal/scriptedinput/input.go +++ b/internal/scriptedinput/input.go @@ -5,10 +5,8 @@ package scriptedinput import ( "context" - "errors" "fmt" "io" - "os" "os/exec" "strconv" "syscall" @@ -65,18 +63,18 @@ func (si *ScriptedInput) scheduleInput(baseDir string, input conf.Input) (bool, } func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) (bool, error) { - if input.Configuration.Stanza.IsDisabled() { - return false, nil - } - intervalS := 3600.0 + intervalS := 3600 for _, p := range input.Configuration.Stanza.Params { if p.Name == "interval" { var err error - intervalS, err = strconv.ParseFloat(p.Value, 64) + intervalS, err = strconv.Atoi(p.Value) if err != nil { return false, err } } + if p.Name == "disabled" && p.Value == "1" { + return false, nil + } } if intervalS == -1 { return false, nil @@ -88,24 +86,19 @@ func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) case <-si.doneChan: return default: - if !si.execute(baseDir, input) { - return - } + si.execute(baseDir, input) } } }() } else { - interval := time.Duration(intervalS * float64(time.Second)) + interval := time.Duration(intervalS) * time.Second go func() { - if !si.execute(baseDir, input) { - return - } + si.execute(baseDir, input) + for { select { case <-time.After(interval): - if !si.execute(baseDir, input) { - return - } + si.execute(baseDir, input) case <-si.doneChan: return } @@ -115,31 +108,10 @@ func (si *ScriptedInput) scheduleScriptedInput(baseDir string, input conf.Input) return true, nil } -// execute runs the script and returns false if the error is permanent -// (e.g. exec format error) and the input should not be retried. -func (si *ScriptedInput) execute(baseDir string, input conf.Input) bool { +func (si *ScriptedInput) execute(baseDir string, input conf.Input) { if err := si._execute(baseDir, input); err != nil { - if isPermanentExecError(err) { - si.logger.Warn("Skipping input permanently due to exec error", zap.String("input", input.Configuration.Stanza.Name), zap.Error(err)) - return false - } - si.logger.Error("Error executing input", zap.String("input", input.Configuration.Stanza.Name), zap.Error(err)) - } - return true -} - -func isPermanentExecError(err error) bool { - var exitErr *exec.ExitError - if errors.As(err, &exitErr) { - if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { - return status.Signal() == syscall.SIGILL - } - } - var pathErr *os.PathError - if errors.As(err, &pathErr) { - return errors.Is(pathErr.Err, syscall.ENOEXEC) || errors.Is(pathErr.Err, syscall.EACCES) + si.logger.Error("Error executing input", zap.String("input", input.Configuration.Stanza.Name), zap.String("error", err.Error())) } - return false } func (si *ScriptedInput) _execute(baseDir string, input conf.Input) error { From 697e11a12046b231495c2149f6a90f8ea6591fa8 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 10:24:19 +0200 Subject: [PATCH 36/46] merge main --- internal/conf/inputs.go | 27 --------------------------- internal/conf/stanza.go | 1 + 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index d3d356e..29836df 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -23,33 +23,6 @@ type Input struct { Configuration Configuration `xml:"configuration"` } -type Configuration struct { - Stanza Stanza `xml:"stanza"` -} - -type Params []Param - -func (p Params) Get(name string) *Param { - for _, param := range p { - if param.Name == name { - return ¶m - } - } - return nil -} - -type Stanza struct { - Name string `xml:"name,attr"` - App string `xml:"app,attr"` - AppDir string `xml:"-"` // app directory used to resolve relative script paths at execution time - Params Params `xml:"param"` -} - -type Param struct { - Name string `xml:"name,attr"` - Value string `xml:",innerxml"` -} - // IsDisabled reports whether the stanza has disabled=1. func (s *Stanza) IsDisabled() bool { p := s.Params.Get("disabled") diff --git a/internal/conf/stanza.go b/internal/conf/stanza.go index 3171ea5..ed81640 100644 --- a/internal/conf/stanza.go +++ b/internal/conf/stanza.go @@ -21,6 +21,7 @@ func (p Params) Get(name string) *Param { type Stanza struct { Name string `xml:"name,attr"` App string `xml:"app,attr"` + AppDir string `xml:"-"` // app directory used to resolve relative script paths at execution time Params Params `xml:"param"` } From 2770687b680a16bb427358e8922ffccc80800b78 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:36:07 +0200 Subject: [PATCH 37/46] rename to splunk_ta* and add tests --- .../etc/apps/disabled/bin/darwin_arm64/foo | 22 ----------- .../etc/apps/disabled/bin/linux_amd64/foo | 22 ----------- .../etc/apps/disabled/default/inputs.conf | 5 --- .../disabled_interval/bin/darwin_arm64/foo | 22 ----------- .../disabled_interval/bin/linux_amd64/foo | 22 ----------- .../disabled_interval/default/inputs.conf | 5 --- .../etc/apps/periodic/bin/darwin_arm64/foo | 13 ------- .../etc/apps/periodic/bin/linux_amd64/foo | 13 ------- .../etc/apps/periodic/default/inputs.conf | 5 --- .../script_home/etc/apps/script/bin/foo.sh | 13 ------- .../etc/apps/script/default/inputs.conf | 5 --- .../ta_home/etc/apps/ta/bin/darwin_arm64/foo | 13 ------- .../ta_home/etc/apps/ta/bin/linux_amd64/foo | 13 ------- .../ta_home/etc/apps/ta/default/inputs.conf | 5 --- .../tcp_home/etc/apps/tcp/default/inputs.conf | 1 - .../udp_home/etc/apps/udp/default/inputs.conf | 1 - internal/conf/inputs_test.go | 16 ++++++++ internal/tabuilder/tabuilder.go | 37 +++++++++++++++---- .../etc/apps/my_ta/default/outputs.conf | 3 -- .../etc/apps/my_ta/local/outputs.conf | 3 -- .../etc/apps/my_ta/default/outputs.conf | 2 - .../etc/apps/my_ta/local/outputs.conf | 2 - .../etc/apps/my_ta/local/outputs.conf | 3 -- .../etc/apps/my_ta/default/transforms.conf | 2 - .../etc/apps/my_ta/local/transforms.conf | 2 - .../etc/apps/my_ta/default/transforms.conf | 2 - .../etc/apps/my_ta/local/transforms.conf | 2 - pkg/splunkinputsreceiver/subreceiver.go | 28 +------------- 28 files changed, 46 insertions(+), 236 deletions(-) delete mode 100755 internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf delete mode 100755 internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf delete mode 100755 internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf delete mode 100755 internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh delete mode 100644 internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf delete mode 100755 internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo delete mode 100755 internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo delete mode 100644 internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf delete mode 100644 internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf delete mode 100644 internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf delete mode 100644 internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/default/outputs.conf delete mode 100644 internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/local/outputs.conf delete mode 100644 internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/default/outputs.conf delete mode 100644 internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/local/outputs.conf delete mode 100644 internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/my_ta/local/outputs.conf delete mode 100644 internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf delete mode 100644 internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf delete mode 100644 internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf delete mode 100644 internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf diff --git a/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo b/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/darwin_arm64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo b/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled_home/etc/apps/disabled/bin/linux_amd64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf b/internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf deleted file mode 100644 index 8d0899e..0000000 --- a/internal/collector/testdata/disabled_home/etc/apps/disabled/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -disabled = 1 -interval = 20 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/darwin_arm64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo deleted file mode 100755 index feaaaef..0000000 --- a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/bin/linux_amd64/foo +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -echo "foo1" -sleep 1 -echo "foo2" -sleep 1 -echo "foo3" -sleep 1 -echo "foo4" -sleep 1 -echo "foo5" -sleep 1 -echo "foo6" -sleep 1 -echo "foo7" -sleep 1 -echo "foo8" -sleep 1 -echo "foo9" -sleep 1 -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf b/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf deleted file mode 100644 index 41a5db9..0000000 --- a/internal/collector/testdata/disabled_interval_home/etc/apps/disabled_interval/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -# Required parameters for the application to function: -interval = -1 # disabled -sourcetype = _foo -index = diff --git a/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo b/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/darwin_arm64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo b/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/periodic_home/etc/apps/periodic/bin/linux_amd64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf b/internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf deleted file mode 100644 index c530bf7..0000000 --- a/internal/collector/testdata/periodic_home/etc/apps/periodic/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -# Required parameters for the application to function: -interval = 1 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh b/internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/script_home/etc/apps/script/bin/foo.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf b/internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf deleted file mode 100644 index 3f6a5a3..0000000 --- a/internal/collector/testdata/script_home/etc/apps/script/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[script://./bin/foo.sh] -# Required parameters for the application to function: -interval = 0 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo b/internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/ta_home/etc/apps/ta/bin/darwin_arm64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo b/internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo deleted file mode 100755 index 3c4d0d9..0000000 --- a/internal/collector/testdata/ta_home/etc/apps/ta/bin/linux_amd64/foo +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash - -echo "foo1" -echo "foo2" -echo "foo3" -echo "foo4" -echo "foo5" -echo "foo6" -echo "foo7" -echo "foo8" -echo "foo9" -echo "foo10" -sleep 1 diff --git a/internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf b/internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf deleted file mode 100644 index fd2b52d..0000000 --- a/internal/collector/testdata/ta_home/etc/apps/ta/default/inputs.conf +++ /dev/null @@ -1,5 +0,0 @@ -[foo] -# Required parameters for the application to function: -interval = 0 -sourcetype = _foo -index = diff --git a/internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf b/internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf deleted file mode 100644 index 640b781..0000000 --- a/internal/collector/testdata/tcp_home/etc/apps/tcp/default/inputs.conf +++ /dev/null @@ -1 +0,0 @@ -[tcp://localhost:4000] diff --git a/internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf b/internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf deleted file mode 100644 index 2ad4d88..0000000 --- a/internal/collector/testdata/udp_home/etc/apps/udp/default/inputs.conf +++ /dev/null @@ -1 +0,0 @@ -[udp://localhost:4000] diff --git a/internal/conf/inputs_test.go b/internal/conf/inputs_test.go index a8224cf..81c7ef5 100644 --- a/internal/conf/inputs_test.go +++ b/internal/conf/inputs_test.go @@ -128,6 +128,22 @@ func TestMergeInputsPartialOverride(t *testing.T) { assert.Equal(t, "override", merged[0].Configuration.Stanza.Params.Get("index").Value) } +func TestMergeInputsFullOverride(t *testing.T) { + base := []Input{{Configuration: Configuration{Stanza: Stanza{ + Name: "monitor:///var/log/syslog", + Params: Params{{Name: "sourcetype", Value: "syslog"}, {Name: "index", Value: "main"}}, + }}}} + override := []Input{{Configuration: Configuration{Stanza: Stanza{ + Name: "monitor:///var/log/syslog", + Params: Params{{Name: "sourcetype", Value: "sourcetype_override"}, {Name: "index", Value: "index_override"}}, + }}}} + + merged := MergeInputs([][]Input{base, override}) + require.Len(t, merged, 1) + assert.Equal(t, "sourcetype_override", merged[0].Configuration.Stanza.Params.Get("sourcetype").Value) + assert.Equal(t, "index_override", merged[0].Configuration.Stanza.Params.Get("index").Value) +} + func TestToXML(t *testing.T) { testStr := ` diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index fb7340d..0f1cdce 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -14,7 +14,7 @@ import ( "fmt" "os" "path/filepath" - "sort" + "strings" "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter" "go.opentelemetry.io/collector/component" @@ -136,18 +136,39 @@ func confFilePaths(dirs []string, filename string) []string { return paths } +// DiscoverTAs returns all direct child directories under splunkHome/etc/apps +// whose name starts with "splunk_ta_" (case-insensitive). +func DiscoverTAs(splunkHome string) ([]string, error) { + appsDir := filepath.Join(splunkHome, "etc", "apps") + entries, err := os.ReadDir(appsDir) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + return nil, fmt.Errorf("tabuilder: failed to scan %s: %w", appsDir, err) + } + var taDirs []string + for _, entry := range entries { + if !entry.IsDir() { + continue + } + if strings.HasPrefix(strings.ToLower(entry.Name()), "splunk_ta_") { + taDirs = append(taDirs, filepath.Join(appsDir, entry.Name())) + } + } + return taDirs, nil +} + func splunkHomeDirs(splunkHome string) []string { + taDirs, _ := DiscoverTAs(splunkHome) etcDir := filepath.Join(splunkHome, "etc") - appDirs, _ := filepath.Glob(filepath.Join(etcDir, "apps", "*")) - sort.Strings(appDirs) - dirs := []string{filepath.Join(etcDir, "system", "default")} - for _, app := range appDirs { - dirs = append(dirs, filepath.Join(app, "default")) + for _, ta := range taDirs { + dirs = append(dirs, filepath.Join(ta, "default")) } - for _, app := range appDirs { - dirs = append(dirs, filepath.Join(app, "local")) + for _, ta := range taDirs { + dirs = append(dirs, filepath.Join(ta, "local")) } dirs = append(dirs, filepath.Join(etcDir, "system", "local")) diff --git a/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/default/outputs.conf b/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/default/outputs.conf deleted file mode 100644 index 0fb45cf..0000000 --- a/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/default/outputs.conf +++ /dev/null @@ -1,3 +0,0 @@ -[httpout] -httpEventCollectorToken = token-app-default -uri = https://app-default:8088/services/collector/event \ No newline at end of file diff --git a/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/local/outputs.conf b/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/local/outputs.conf deleted file mode 100644 index aaf8b86..0000000 --- a/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/my_ta/local/outputs.conf +++ /dev/null @@ -1,3 +0,0 @@ -[httpout] -httpEventCollectorToken = token-app-local -uri = https://app-local:8088/services/collector/event diff --git a/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/default/outputs.conf b/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/default/outputs.conf deleted file mode 100644 index b8ed7f2..0000000 --- a/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/default/outputs.conf +++ /dev/null @@ -1,2 +0,0 @@ -[httpout] -httpEventCollectorToken = token-app-default diff --git a/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/local/outputs.conf b/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/local/outputs.conf deleted file mode 100644 index d317bf9..0000000 --- a/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/my_ta/local/outputs.conf +++ /dev/null @@ -1,2 +0,0 @@ -[httpout] -uri = https://app-local:8088/services/collector/event diff --git a/internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/my_ta/local/outputs.conf b/internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/my_ta/local/outputs.conf deleted file mode 100644 index aaf8b86..0000000 --- a/internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/my_ta/local/outputs.conf +++ /dev/null @@ -1,3 +0,0 @@ -[httpout] -httpEventCollectorToken = token-app-local -uri = https://app-local:8088/services/collector/event diff --git a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf deleted file mode 100644 index eb3772d..0000000 --- a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/default/transforms.conf +++ /dev/null @@ -1,2 +0,0 @@ -[example_transform] -REGEX=default diff --git a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf deleted file mode 100644 index 7813599..0000000 --- a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/my_ta/local/transforms.conf +++ /dev/null @@ -1,2 +0,0 @@ -[example_transform] -REGEX=local \ No newline at end of file diff --git a/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf deleted file mode 100644 index 51c987f..0000000 --- a/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/my_ta/default/transforms.conf +++ /dev/null @@ -1,2 +0,0 @@ -[example_default] -REGEX=default \ No newline at end of file diff --git a/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf deleted file mode 100644 index 071044b..0000000 --- a/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/my_ta/local/transforms.conf +++ /dev/null @@ -1,2 +0,0 @@ -[example_local] -REGEX=local \ No newline at end of file diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 72fd252..14d0781 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -5,10 +5,7 @@ package splunkinputsreceiver import ( "context" - "errors" "fmt" - "os" - "path/filepath" "strings" "go.opentelemetry.io/collector/component" @@ -93,7 +90,7 @@ func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Se return nil, err } - taDirs, err := discoverTAs(splunkHome) + taDirs, err := tabuilder.DiscoverTAs(splunkHome) if err != nil { return nil, err } @@ -122,29 +119,6 @@ func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Se return packReceivers(allReceivers), nil } -// discoverTAs returns all direct child directories under $SPLUNK_HOME/etc/apps -// whose name starts with "splunk_ta_" (case-insensitive). -func discoverTAs(splunkHome string) ([]string, error) { - appsDir := filepath.Join(splunkHome, "etc", "apps") - entries, err := os.ReadDir(appsDir) - if err != nil { - if errors.Is(err, os.ErrNotExist) { - return nil, nil - } - return nil, fmt.Errorf("splunk_inputs: failed to scan %s: %w", appsDir, err) - } - var taDirs []string - for _, entry := range entries { - if !entry.IsDir() { - continue - } - if strings.HasPrefix(strings.ToLower(entry.Name()), "splunk_ta_") { - taDirs = append(taDirs, filepath.Join(appsDir, entry.Name())) - } - } - return taDirs, nil -} - func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, transforms []Transform, props []Prop, baseDir string, next consumer.Logs, settings receiver.Settings) ([]receiver.Logs, error) { var receivers []receiver.Logs for _, input := range inputs { From 8fe7988ea4a08590479db2527954e21b0c4d8115 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:37:49 +0200 Subject: [PATCH 38/46] move testdata --- .../splunk_ta_disabled/bin/darwin_arm64/foo | 22 +++++++++++++++++++ .../splunk_ta_disabled/bin/linux_amd64/foo | 22 +++++++++++++++++++ .../splunk_ta_disabled/default/inputs.conf | 5 +++++ .../bin/darwin_arm64/foo | 22 +++++++++++++++++++ .../bin/linux_amd64/foo | 22 +++++++++++++++++++ .../default/inputs.conf | 5 +++++ .../splunk_ta_periodic/bin/darwin_arm64/foo | 13 +++++++++++ .../splunk_ta_periodic/bin/linux_amd64/foo | 13 +++++++++++ .../splunk_ta_periodic/default/inputs.conf | 5 +++++ .../etc/apps/splunk_ta_script/bin/foo.sh | 13 +++++++++++ .../apps/splunk_ta_script/default/inputs.conf | 5 +++++ .../apps/splunk_ta_test/bin/darwin_arm64/foo | 13 +++++++++++ .../apps/splunk_ta_test/bin/linux_amd64/foo | 13 +++++++++++ .../apps/splunk_ta_test/default/inputs.conf | 5 +++++ .../apps/splunk_ta_tcp/default/inputs.conf | 1 + .../apps/splunk_ta_udp/default/inputs.conf | 1 + .../apps/splunk_ta_test/default/outputs.conf | 3 +++ .../apps/splunk_ta_test/local/outputs.conf | 3 +++ .../apps/splunk_ta_test/default/outputs.conf | 2 ++ .../apps/splunk_ta_test/local/outputs.conf | 2 ++ .../apps/splunk_ta_test/local/outputs.conf | 3 +++ .../splunk_ta_test/default/transforms.conf | 2 ++ .../apps/splunk_ta_test/local/transforms.conf | 2 ++ .../splunk_ta_test/default/transforms.conf | 2 ++ .../apps/splunk_ta_test/local/transforms.conf | 2 ++ 25 files changed, 201 insertions(+) create mode 100755 internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/default/inputs.conf create mode 100755 internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/default/inputs.conf create mode 100755 internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/default/inputs.conf create mode 100755 internal/collector/testdata/script_home/etc/apps/splunk_ta_script/bin/foo.sh create mode 100644 internal/collector/testdata/script_home/etc/apps/splunk_ta_script/default/inputs.conf create mode 100755 internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/darwin_arm64/foo create mode 100755 internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/linux_amd64/foo create mode 100644 internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/default/inputs.conf create mode 100644 internal/collector/testdata/tcp_home/etc/apps/splunk_ta_tcp/default/inputs.conf create mode 100644 internal/collector/testdata/udp_home/etc/apps/splunk_ta_udp/default/inputs.conf create mode 100644 internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/default/outputs.conf create mode 100644 internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/local/outputs.conf create mode 100644 internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/default/outputs.conf create mode 100644 internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/local/outputs.conf create mode 100644 internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/splunk_ta_test/local/outputs.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/default/transforms.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/local/transforms.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_default/etc/apps/splunk_ta_test/default/transforms.conf create mode 100644 internal/tabuilder/testdata/transforms/splunk_local/etc/apps/splunk_ta_test/local/transforms.conf diff --git a/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/darwin_arm64/foo b/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/darwin_arm64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/darwin_arm64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/linux_amd64/foo b/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/linux_amd64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/bin/linux_amd64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/default/inputs.conf b/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/default/inputs.conf new file mode 100644 index 0000000..8d0899e --- /dev/null +++ b/internal/collector/testdata/disabled_home/etc/apps/splunk_ta_disabled/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +disabled = 1 +interval = 20 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/darwin_arm64/foo b/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/darwin_arm64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/darwin_arm64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/linux_amd64/foo b/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/linux_amd64/foo new file mode 100755 index 0000000..feaaaef --- /dev/null +++ b/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/bin/linux_amd64/foo @@ -0,0 +1,22 @@ +#!/bin/bash + +echo "foo1" +sleep 1 +echo "foo2" +sleep 1 +echo "foo3" +sleep 1 +echo "foo4" +sleep 1 +echo "foo5" +sleep 1 +echo "foo6" +sleep 1 +echo "foo7" +sleep 1 +echo "foo8" +sleep 1 +echo "foo9" +sleep 1 +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/default/inputs.conf b/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/default/inputs.conf new file mode 100644 index 0000000..41a5db9 --- /dev/null +++ b/internal/collector/testdata/disabled_interval_home/etc/apps/splunk_ta_disabled_interval/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +# Required parameters for the application to function: +interval = -1 # disabled +sourcetype = _foo +index = diff --git a/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/darwin_arm64/foo b/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/darwin_arm64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/darwin_arm64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/linux_amd64/foo b/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/linux_amd64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/bin/linux_amd64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/default/inputs.conf b/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/default/inputs.conf new file mode 100644 index 0000000..c530bf7 --- /dev/null +++ b/internal/collector/testdata/periodic_home/etc/apps/splunk_ta_periodic/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +# Required parameters for the application to function: +interval = 1 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/script_home/etc/apps/splunk_ta_script/bin/foo.sh b/internal/collector/testdata/script_home/etc/apps/splunk_ta_script/bin/foo.sh new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/script_home/etc/apps/splunk_ta_script/bin/foo.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/script_home/etc/apps/splunk_ta_script/default/inputs.conf b/internal/collector/testdata/script_home/etc/apps/splunk_ta_script/default/inputs.conf new file mode 100644 index 0000000..3f6a5a3 --- /dev/null +++ b/internal/collector/testdata/script_home/etc/apps/splunk_ta_script/default/inputs.conf @@ -0,0 +1,5 @@ +[script://./bin/foo.sh] +# Required parameters for the application to function: +interval = 0 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/darwin_arm64/foo b/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/darwin_arm64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/darwin_arm64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/linux_amd64/foo b/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/linux_amd64/foo new file mode 100755 index 0000000..3c4d0d9 --- /dev/null +++ b/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/bin/linux_amd64/foo @@ -0,0 +1,13 @@ +#!/bin/bash + +echo "foo1" +echo "foo2" +echo "foo3" +echo "foo4" +echo "foo5" +echo "foo6" +echo "foo7" +echo "foo8" +echo "foo9" +echo "foo10" +sleep 1 diff --git a/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/default/inputs.conf b/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/default/inputs.conf new file mode 100644 index 0000000..fd2b52d --- /dev/null +++ b/internal/collector/testdata/ta_home/etc/apps/splunk_ta_test/default/inputs.conf @@ -0,0 +1,5 @@ +[foo] +# Required parameters for the application to function: +interval = 0 +sourcetype = _foo +index = diff --git a/internal/collector/testdata/tcp_home/etc/apps/splunk_ta_tcp/default/inputs.conf b/internal/collector/testdata/tcp_home/etc/apps/splunk_ta_tcp/default/inputs.conf new file mode 100644 index 0000000..640b781 --- /dev/null +++ b/internal/collector/testdata/tcp_home/etc/apps/splunk_ta_tcp/default/inputs.conf @@ -0,0 +1 @@ +[tcp://localhost:4000] diff --git a/internal/collector/testdata/udp_home/etc/apps/splunk_ta_udp/default/inputs.conf b/internal/collector/testdata/udp_home/etc/apps/splunk_ta_udp/default/inputs.conf new file mode 100644 index 0000000..2ad4d88 --- /dev/null +++ b/internal/collector/testdata/udp_home/etc/apps/splunk_ta_udp/default/inputs.conf @@ -0,0 +1 @@ +[udp://localhost:4000] diff --git a/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/default/outputs.conf b/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/default/outputs.conf new file mode 100644 index 0000000..0fb45cf --- /dev/null +++ b/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/default/outputs.conf @@ -0,0 +1,3 @@ +[httpout] +httpEventCollectorToken = token-app-default +uri = https://app-default:8088/services/collector/event \ No newline at end of file diff --git a/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/local/outputs.conf b/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/local/outputs.conf new file mode 100644 index 0000000..aaf8b86 --- /dev/null +++ b/internal/tabuilder/testdata/outputs/app_local_wins_over_app_default/etc/apps/splunk_ta_test/local/outputs.conf @@ -0,0 +1,3 @@ +[httpout] +httpEventCollectorToken = token-app-local +uri = https://app-local:8088/services/collector/event diff --git a/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/default/outputs.conf b/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/default/outputs.conf new file mode 100644 index 0000000..b8ed7f2 --- /dev/null +++ b/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/default/outputs.conf @@ -0,0 +1,2 @@ +[httpout] +httpEventCollectorToken = token-app-default diff --git a/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/local/outputs.conf b/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/local/outputs.conf new file mode 100644 index 0000000..d317bf9 --- /dev/null +++ b/internal/tabuilder/testdata/outputs/app_overrides/etc/apps/splunk_ta_test/local/outputs.conf @@ -0,0 +1,2 @@ +[httpout] +uri = https://app-local:8088/services/collector/event diff --git a/internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/splunk_ta_test/local/outputs.conf b/internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/splunk_ta_test/local/outputs.conf new file mode 100644 index 0000000..aaf8b86 --- /dev/null +++ b/internal/tabuilder/testdata/outputs/system_local_wins/etc/apps/splunk_ta_test/local/outputs.conf @@ -0,0 +1,3 @@ +[httpout] +httpEventCollectorToken = token-app-local +uri = https://app-local:8088/services/collector/event diff --git a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/default/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/default/transforms.conf new file mode 100644 index 0000000..eb3772d --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/default/transforms.conf @@ -0,0 +1,2 @@ +[example_transform] +REGEX=default diff --git a/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/local/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/local/transforms.conf new file mode 100644 index 0000000..7813599 --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_both/etc/apps/splunk_ta_test/local/transforms.conf @@ -0,0 +1,2 @@ +[example_transform] +REGEX=local \ No newline at end of file diff --git a/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/splunk_ta_test/default/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/splunk_ta_test/default/transforms.conf new file mode 100644 index 0000000..51c987f --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_default/etc/apps/splunk_ta_test/default/transforms.conf @@ -0,0 +1,2 @@ +[example_default] +REGEX=default \ No newline at end of file diff --git a/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/splunk_ta_test/local/transforms.conf b/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/splunk_ta_test/local/transforms.conf new file mode 100644 index 0000000..071044b --- /dev/null +++ b/internal/tabuilder/testdata/transforms/splunk_local/etc/apps/splunk_ta_test/local/transforms.conf @@ -0,0 +1,2 @@ +[example_local] +REGEX=local \ No newline at end of file From 3a1f9572ba161fa50bab0335d3d5538d3fcf5fac Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:43:34 +0200 Subject: [PATCH 39/46] go mod tidy --- go.mod | 1 - go.sum | 2 -- 2 files changed, 3 deletions(-) diff --git a/go.mod b/go.mod index 00da339..e42ef59 100644 --- a/go.mod +++ b/go.mod @@ -67,7 +67,6 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.157.0 // indirect - github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/sharedcomponent v0.157.0 // indirect github.com/open-telemetry/opentelemetry-collector-contrib/internal/splunk v0.157.0 // indirect diff --git a/go.sum b/go.sum index 2da8c6b..e1e31c6 100644 --- a/go.sum +++ b/go.sum @@ -142,8 +142,6 @@ github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexpo github.com/open-telemetry/opentelemetry-collector-contrib/exporter/splunkhecexporter v0.157.0/go.mod h1:EEHR9WkKjnZA30xrn7tz6jiczE49RVJnQOgLqS4nJDE= github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.157.0 h1:biFNL7OCavTRGSRqHuu83uw6yDu4tUPHhze0FN48aOE= github.com/open-telemetry/opentelemetry-collector-contrib/extension/ackextension v0.157.0/go.mod h1:XcMDJnqBbYCENJloY7w8t5f09SIB1H/R2QOpIUa4U3o= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0 h1:4t8wbN7CzwAra4dW7EjtqO+Gt3qJoAEP87vu/KuKfZ8= -github.com/open-telemetry/opentelemetry-collector-contrib/extension/observer v0.157.0/go.mod h1:fc+AF4Np1AMkDIxtZSPduXV/FTl6VeME3rp+ZsawcfY= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0 h1:ZUHfd8qumKAMctr0CnRdsMjfke/RBU8702O3auyWMtM= github.com/open-telemetry/opentelemetry-collector-contrib/extension/storage v0.157.0/go.mod h1:x6bJlY9KOnHTbRqKhdmR5jbutjunFsDC5r+OP60+RRU= github.com/open-telemetry/opentelemetry-collector-contrib/internal/common v0.157.0 h1:aQVvm4iiT1DUx2CzeDLEWETDKgbBvWAPJgancj6SQXY= From 79ce2351693ae18f5f9efe57f0a6176b98630953 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:58:09 +0200 Subject: [PATCH 40/46] handle unsupported stanza --- internal/tabuilder/tabuilder.go | 4 +++- pkg/splunkinputsreceiver/subreceiver.go | 4 ++++ pkg/splunkoutputsexporter/factory_test.go | 10 ++++++---- pkg/splunkoutputsexporter/subexporter.go | 5 +++++ 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 0f1cdce..198e194 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -309,13 +309,15 @@ func CreateExporter(merged conf.ConfMap, logger *zap.Logger, telemetrySettings c } // CreateOutputExporter builds a logs exporter from one built-in output stanza. +// Returns nil (no error) for unsupported output kinds. func CreateOutputExporter(output *conf.Output, logger *zap.Logger, telemetrySettings component.TelemetrySettings) (exporter.Logs, error) { parsed, err := stanza.ParseOutputName(output.Configuration.Stanza.Name) if err != nil { return nil, err } if parsed.Kind != "httpout" { - return nil, fmt.Errorf("unsupported scheme %q", parsed.Kind) + // unsupported kind + return nil, nil } return newHECExporter(output, logger, telemetrySettings) } diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 14d0781..139b78a 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -131,6 +131,10 @@ func (o factoryOptions) createReceivers(ctx context.Context, inputs []Input, tra if err != nil { return nil, fmt.Errorf("failed to create receiver %q: %w", name, err) } + if l == nil { + settings.Logger.Info("splunk_inputs: skipping unsupported input stanza", zap.String("stanza", name)) + continue + } receivers = append(receivers, l) } return receivers, nil diff --git a/pkg/splunkoutputsexporter/factory_test.go b/pkg/splunkoutputsexporter/factory_test.go index 9abaf94..fbe7da9 100644 --- a/pkg/splunkoutputsexporter/factory_test.go +++ b/pkg/splunkoutputsexporter/factory_test.go @@ -14,6 +14,7 @@ import ( "go.opentelemetry.io/collector/consumer" "go.opentelemetry.io/collector/exporter" "go.opentelemetry.io/collector/pdata/plog" + "go.uber.org/zap" "github.com/splunk/tarunner/pkg/splunkoutputsexporter" ) @@ -71,13 +72,13 @@ func TestWithSubExporterRegistersCustomScheme(t *testing.T) { require.Equal(t, "splunk:9997", fake.requests[0].Output.Configuration.Stanza.Params.Get("server").Value) } -func TestWithSubExporterRejectsUnsupportedScheme(t *testing.T) { +func TestWithSubExporterSkipsUnsupportedScheme(t *testing.T) { baseDir := writeTA(t, "[tcpout:primary]\nserver = splunk:9997\n") factory := splunkoutputsexporter.NewFactory() exp, err := factory.CreateLogs(context.Background(), newExporterSettings(), splunkoutputsexporter.Config{BaseDir: baseDir}) - require.ErrorContains(t, err, `unsupported scheme "tcpout"`) - require.Nil(t, exp) + require.NoError(t, err) + require.NotNil(t, exp) } type fakeSubExporterFactory struct { @@ -114,7 +115,8 @@ func (fakeLogsExporter) ConsumeLogs(context.Context, plog.Logs) error { func newExporterSettings() exporter.Settings { return exporter.Settings{ - ID: component.MustNewID("splunk_outputs"), + ID: component.MustNewID("splunk_outputs"), + TelemetrySettings: component.TelemetrySettings{Logger: zap.NewNop()}, } } diff --git a/pkg/splunkoutputsexporter/subexporter.go b/pkg/splunkoutputsexporter/subexporter.go index 2fc2540..6c2af9f 100644 --- a/pkg/splunkoutputsexporter/subexporter.go +++ b/pkg/splunkoutputsexporter/subexporter.go @@ -11,6 +11,7 @@ import ( "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/exporter" + "go.uber.org/zap" "github.com/splunk/tarunner/internal/conf" "github.com/splunk/tarunner/internal/stanza" @@ -103,6 +104,10 @@ func (o factoryOptions) createExporters(ctx context.Context, baseDir string, out if err != nil { return nil, fmt.Errorf("failed to create exporter %q: %w", output.Configuration.Stanza.Name, err) } + if e == nil { + settings.Logger.Info("splunk_outputs: skipping unsupported output stanza", zap.String("stanza", output.Configuration.Stanza.Name)) + continue + } exporters = append(exporters, e) } return exporters, nil From b013240b3ac13a5349635236bb863e971c0ead5e Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:07:33 +0200 Subject: [PATCH 41/46] add changelog --- .chloggen/splunk-inputs-precedence.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .chloggen/splunk-inputs-precedence.yaml diff --git a/.chloggen/splunk-inputs-precedence.yaml b/.chloggen/splunk-inputs-precedence.yaml new file mode 100644 index 0000000..f6332c4 --- /dev/null +++ b/.chloggen/splunk-inputs-precedence.yaml @@ -0,0 +1,14 @@ +change_type: enhancement + +component: splunk_inputs + +note: "add Splunk btool config precedence across system and `splunk_ta_*` app directories" + +issues: [117] + +subtext: | + The `base_dir` config field (or `$SPLUNK_HOME` env var) now points to the Splunk + installation root. The receiver discovers all `splunk_ta_*` directories automatically + and merges `inputs.conf`, `transforms.conf`, and `props.conf` per TA using standard + btool precedence — later layers win per key. Unsupported input and output stanza + kinds are now skipped silently with an info log instead of returning an error. From 60245c9e2f0a8bae0131c58b78cd58c190c2bc8c Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:20:30 +0200 Subject: [PATCH 42/46] minimise the comments --- internal/conf/inputs.go | 12 +----------- internal/tabuilder/tabuilder.go | 13 +++---------- 2 files changed, 4 insertions(+), 21 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index 29836df..5aef183 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -29,10 +29,6 @@ func (s *Stanza) IsDisabled() bool { return p != nil && p.Value == "1" } -// ReadInput parses an inputs.conf payload. appDir is the app directory -// (e.g. $SPLUNK_HOME/etc/apps/my_ta) stored on each stanza for use when -// resolving relative script paths at execution time. Pass an empty string -// when the app directory is unknown or irrelevant. func ReadInput(payload []byte, appDir string) ([]Input, error) { f, err := ini.Load(payload) if err != nil { @@ -69,10 +65,7 @@ func ReadInput(payload []byte, appDir string) ([]Input, error) { return result, nil } -// MergeInputs merges multiple slices of inputs, with later slices taking -// precedence over earlier ones (local/ wins over default/). -// Stanzas are keyed by name; params are merged key by key so that a later -// layer only overrides the keys it explicitly sets. +// MergeInputs merges layered inputs; later layers take precedence per param key. func MergeInputs(layers [][]Input) []Input { seen := make(map[string]int) var result []Input @@ -90,9 +83,6 @@ func MergeInputs(layers [][]Input) []Input { return result } -// mergeInput merges override into base at the param level. -// Keys present in override replace the same key in base; keys only in base -// are preserved. func mergeInput(base, override Input) Input { merged := base params := make(map[string]int, len(base.Configuration.Stanza.Params)) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 198e194..744df0b 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -136,8 +136,7 @@ func confFilePaths(dirs []string, filename string) []string { return paths } -// DiscoverTAs returns all direct child directories under splunkHome/etc/apps -// whose name starts with "splunk_ta_" (case-insensitive). +// DiscoverTAs returns splunk_ta_* directories under splunkHome/etc/apps. func DiscoverTAs(splunkHome string) ([]string, error) { appsDir := filepath.Join(splunkHome, "etc", "apps") entries, err := os.ReadDir(appsDir) @@ -175,9 +174,6 @@ func splunkHomeDirs(splunkHome string) []string { return dirs } -// taDirsWithSystem returns the conf search path for a single TA directory merged -// with system-level config from splunkHome, following Splunk precedence: -// system default → TA default → TA local → system local. func taDirsWithSystem(splunkHome, taDir string) []string { etcDir := filepath.Join(splunkHome, "etc") return []string{ @@ -203,15 +199,12 @@ func readConfFiles(paths []string) ([][]byte, error) { return payloads, nil } -// ConfDirs returns the conf search path for a Splunk home directory, following -// standard Splunk precedence across all apps. +// ConfDirs returns the Splunk btool conf search path for splunkHome. func ConfDirs(splunkHome string) []string { return splunkHomeDirs(splunkHome) } -// ConfDirsWithSystem returns the conf search path for a single TA directory -// combined with system-level config from splunkHome, following Splunk -// precedence: system default → TA default → TA local → system local. +// ConfDirsWithSystem returns the Splunk btool conf search path for a single TA merged with system config. func ConfDirsWithSystem(splunkHome, taDir string) []string { return taDirsWithSystem(splunkHome, taDir) } From d5777670b066eedfcfa6ed04d3339ab9224911d2 Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:39:31 +0200 Subject: [PATCH 43/46] fix misleading comments --- internal/tabuilder/tabuilder.go | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index 744df0b..bf9ba30 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -60,7 +60,7 @@ func CreateReceivers(ctx context.Context, inputs []conf.Input, transforms []conf } // CreateReceiver builds a single logs receiver for an input stanza. -// Returns nil (no error) for unsupported input kinds. +// Returns nil receiver for unsupported input kinds; the caller is responsible for logging. func CreateReceiver(ctx context.Context, baseDir string, next consumer.Logs, input conf.Input, transforms []conf.Transform, props []conf.Prop, telemetrySettings component.TelemetrySettings) (receiver.Logs, error) { parsed, err := stanza.ParseName(input.Configuration.Stanza.Name) if err != nil { @@ -116,7 +116,6 @@ func CreateReceiver(ctx context.Context, baseDir string, next consumer.Logs, inp Props: props, }, next) default: - // unsupported kind — skip silently return nil, nil } } @@ -302,14 +301,13 @@ func CreateExporter(merged conf.ConfMap, logger *zap.Logger, telemetrySettings c } // CreateOutputExporter builds a logs exporter from one built-in output stanza. -// Returns nil (no error) for unsupported output kinds. +// Returns nil exporter for unsupported output kinds; the caller is responsible for logging. func CreateOutputExporter(output *conf.Output, logger *zap.Logger, telemetrySettings component.TelemetrySettings) (exporter.Logs, error) { parsed, err := stanza.ParseOutputName(output.Configuration.Stanza.Name) if err != nil { return nil, err } if parsed.Kind != "httpout" { - // unsupported kind return nil, nil } return newHECExporter(output, logger, telemetrySettings) From 60522f987994a0ff2cd8bd1a4709ff987c1619ab Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 27 Aug 2026 09:18:46 +0200 Subject: [PATCH 44/46] move AppDir to input struct --- internal/conf/inputs.go | 3 ++- internal/conf/stanza.go | 1 - internal/script/command.go | 7 ++----- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index 5aef183..6d076e4 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -21,6 +21,7 @@ type Input struct { SessionKey string `xml:"session_key"` CheckpointDir string `xml:"checkpoint_dir"` Configuration Configuration `xml:"configuration"` + AppDir string `xml:"-"` // app directory used to resolve relative script paths at execution time } // IsDisabled reports whether the stanza has disabled=1. @@ -41,11 +42,11 @@ func ReadInput(payload []byte, appDir string) ([]Input, error) { continue // disregard default section. We need a stanza per input. } i := Input{ + AppDir: appDir, Configuration: Configuration{ Stanza: Stanza{ Name: section.Name(), App: appName, - AppDir: appDir, Params: make([]Param, len(section.Keys())), }, }, diff --git a/internal/conf/stanza.go b/internal/conf/stanza.go index ed81640..3171ea5 100644 --- a/internal/conf/stanza.go +++ b/internal/conf/stanza.go @@ -21,7 +21,6 @@ func (p Params) Get(name string) *Param { type Stanza struct { Name string `xml:"name,attr"` App string `xml:"app,attr"` - AppDir string `xml:"-"` // app directory used to resolve relative script paths at execution time Params Params `xml:"param"` } diff --git a/internal/script/command.go b/internal/script/command.go index 6f1469e..2161f62 100644 --- a/internal/script/command.go +++ b/internal/script/command.go @@ -18,12 +18,9 @@ func DetermineCommandName(baseDir string, input conf.Input) (string, error) { if err != nil { return "", err } - // Use AppDir from the stanza when available; it points to the specific app - // directory the conf was parsed from, which is more accurate than baseDir - // when multiple apps are layered together. resolveDir := baseDir - if input.Configuration.Stanza.AppDir != "" { - resolveDir = input.Configuration.Stanza.AppDir + if input.AppDir != "" { + resolveDir = input.AppDir } switch parsed.Kind { case "monitor", "batch": From cf4efb6a53c8ce91b5c23f962ceedea8b1b8d0fb Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:45:41 +0200 Subject: [PATCH 45/46] make ResolveSplunkHome global and use it in both inputs and outputs --- internal/tabuilder/tabuilder.go | 11 +++++++++++ pkg/splunkinputsreceiver/receiver.go | 14 -------------- pkg/splunkinputsreceiver/subreceiver.go | 4 ++-- pkg/splunkoutputsexporter/subexporter.go | 10 +++------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/internal/tabuilder/tabuilder.go b/internal/tabuilder/tabuilder.go index bf9ba30..c202c65 100644 --- a/internal/tabuilder/tabuilder.go +++ b/internal/tabuilder/tabuilder.go @@ -38,6 +38,17 @@ import ( "github.com/splunk/tarunner/internal/stanza" ) +// ResolveSplunkHome returns baseDir if set, otherwise falls back to $SPLUNK_HOME. +func ResolveSplunkHome(baseDir string) (string, error) { + if baseDir != "" { + return baseDir, nil + } + if home := os.Getenv("SPLUNK_HOME"); home != "" { + return home, nil + } + return "", fmt.Errorf("base_dir is not set and $SPLUNK_HOME is not defined") +} + // CreateReceivers builds a logs receiver for every enabled input stanza, // dispatching by the stanza name's input kind. Stanzas with disabled=1 or // unsupported kinds are skipped silently. diff --git a/pkg/splunkinputsreceiver/receiver.go b/pkg/splunkinputsreceiver/receiver.go index f7b1d73..aa0585d 100644 --- a/pkg/splunkinputsreceiver/receiver.go +++ b/pkg/splunkinputsreceiver/receiver.go @@ -6,8 +6,6 @@ package splunkinputsreceiver import ( "context" "errors" - "fmt" - "os" "go.opentelemetry.io/collector/component" "go.opentelemetry.io/collector/receiver" @@ -52,15 +50,3 @@ func packReceivers(receivers []receiver.Logs) receiver.Logs { } } } - -// resolveSplunkHome returns the Splunk installation root from the config, -// falling back to the $SPLUNK_HOME environment variable. -func resolveSplunkHome(cfg Config) (string, error) { - if cfg.BaseDir != "" { - return cfg.BaseDir, nil - } - if home := os.Getenv("SPLUNK_HOME"); home != "" { - return home, nil - } - return "", fmt.Errorf("splunk_inputs: base_dir is not set and $SPLUNK_HOME is not defined") -} diff --git a/pkg/splunkinputsreceiver/subreceiver.go b/pkg/splunkinputsreceiver/subreceiver.go index 139b78a..b4e6bd4 100644 --- a/pkg/splunkinputsreceiver/subreceiver.go +++ b/pkg/splunkinputsreceiver/subreceiver.go @@ -85,9 +85,9 @@ func newFactoryOptions(opts ...Option) factoryOptions { func (o factoryOptions) createLogsFunc(ctx context.Context, settings receiver.Settings, config component.Config, logs consumer.Logs) (receiver.Logs, error) { cfg := config.(Config) - splunkHome, err := resolveSplunkHome(cfg) + splunkHome, err := tabuilder.ResolveSplunkHome(cfg.BaseDir) if err != nil { - return nil, err + return nil, fmt.Errorf("splunk_inputs: %w", err) } taDirs, err := tabuilder.DiscoverTAs(splunkHome) diff --git a/pkg/splunkoutputsexporter/subexporter.go b/pkg/splunkoutputsexporter/subexporter.go index 6c2af9f..17639ae 100644 --- a/pkg/splunkoutputsexporter/subexporter.go +++ b/pkg/splunkoutputsexporter/subexporter.go @@ -6,7 +6,6 @@ package splunkoutputsexporter import ( "context" "fmt" - "os" "strings" "go.opentelemetry.io/collector/component" @@ -77,12 +76,9 @@ func newFactoryOptions(opts ...Option) factoryOptions { func (o factoryOptions) createLogsFunc(ctx context.Context, settings exporter.Settings, config component.Config) (exporter.Logs, error) { cfg := config.(Config) - splunkHome := cfg.BaseDir - if splunkHome == "" { - splunkHome = os.Getenv("SPLUNK_HOME") - } - if splunkHome == "" { - return nil, fmt.Errorf("splunk_outputs: base_dir is not set and SPLUNK_HOME is not defined") + splunkHome, err := tabuilder.ResolveSplunkHome(cfg.BaseDir) + if err != nil { + return nil, fmt.Errorf("splunk_outputs: %w", err) } outputs, err := tabuilder.ReadOutputGroups(splunkHome) From ce601b6842597cf21081076c47e75cd08c0687bc Mon Sep 17 00:00:00 2001 From: Olga <86965961+omrozowicz-splunk@users.noreply.github.com> Date: Thu, 27 Aug 2026 10:47:10 +0200 Subject: [PATCH 46/46] run gofmt --- internal/conf/inputs.go | 2 +- internal/receiver/batchreceiver/config.go | 5 ++--- internal/receiver/monitorreceiver/config.go | 5 ++--- internal/receiver/scriptreceiver/config.go | 2 +- internal/receiver/tcpreceiver/config.go | 5 ++--- internal/receiver/udpreceiver/config.go | 5 ++--- internal/receiver/wineventlogreceiver/config.go | 5 ++--- 7 files changed, 12 insertions(+), 17 deletions(-) diff --git a/internal/conf/inputs.go b/internal/conf/inputs.go index 6d076e4..9e3374e 100644 --- a/internal/conf/inputs.go +++ b/internal/conf/inputs.go @@ -20,8 +20,8 @@ type Input struct { ServerURI string `xml:"server_uri"` SessionKey string `xml:"session_key"` CheckpointDir string `xml:"checkpoint_dir"` + AppDir string `xml:"-"` Configuration Configuration `xml:"configuration"` - AppDir string `xml:"-"` // app directory used to resolve relative script paths at execution time } // IsDisabled reports whether the stanza has disabled=1. diff --git a/internal/receiver/batchreceiver/config.go b/internal/receiver/batchreceiver/config.go index 159d714..b7ba1ee 100644 --- a/internal/receiver/batchreceiver/config.go +++ b/internal/receiver/batchreceiver/config.go @@ -8,9 +8,8 @@ import ( ) type Config struct { + Input conf.Input `mapstructure:"-"` + BaseDir string `mapstructure:"-"` Transforms []conf.Transform `mapstructure:"-"` Props []conf.Prop `mapstructure:"-"` - - BaseDir string `mapstructure:"-"` - Input conf.Input `mapstructure:"-"` } diff --git a/internal/receiver/monitorreceiver/config.go b/internal/receiver/monitorreceiver/config.go index 31d37a8..855f4ad 100644 --- a/internal/receiver/monitorreceiver/config.go +++ b/internal/receiver/monitorreceiver/config.go @@ -8,9 +8,8 @@ import ( ) type Config struct { + Input conf.Input `mapstructure:"-"` + BaseDir string `mapstructure:"-"` Transforms []conf.Transform `mapstructure:"-"` Props []conf.Prop `mapstructure:"-"` - - BaseDir string `mapstructure:"-"` - Input conf.Input `mapstructure:"-"` } diff --git a/internal/receiver/scriptreceiver/config.go b/internal/receiver/scriptreceiver/config.go index 38889cc..7ce0228 100644 --- a/internal/receiver/scriptreceiver/config.go +++ b/internal/receiver/scriptreceiver/config.go @@ -6,8 +6,8 @@ package scriptreceiver import "github.com/splunk/tarunner/internal/conf" type Config struct { + conf.Input `mapstructure:"-"` BaseDir string `mapstructure:"-"` Props []conf.Prop `mapstructure:"-"` Transforms []conf.Transform `mapstructure:"-"` - conf.Input `mapstructure:"-"` } diff --git a/internal/receiver/tcpreceiver/config.go b/internal/receiver/tcpreceiver/config.go index 4aa7592..08df79d 100644 --- a/internal/receiver/tcpreceiver/config.go +++ b/internal/receiver/tcpreceiver/config.go @@ -9,11 +9,10 @@ import ( ) type Config struct { + Input conf.Input `mapstructure:"-"` + BaseDir string `mapstructure:"-"` Transforms []conf.Transform `mapstructure:"-"` Props []conf.Prop `mapstructure:"-"` - - BaseDir string `mapstructure:"-"` - Input conf.Input `mapstructure:"-"` } func (cfg *Config) Validate() error { diff --git a/internal/receiver/udpreceiver/config.go b/internal/receiver/udpreceiver/config.go index ef33801..f180647 100644 --- a/internal/receiver/udpreceiver/config.go +++ b/internal/receiver/udpreceiver/config.go @@ -9,11 +9,10 @@ import ( ) type Config struct { + Input conf.Input `mapstructure:"-"` + BaseDir string `mapstructure:"-"` Transforms []conf.Transform `mapstructure:"-"` Props []conf.Prop `mapstructure:"-"` - - BaseDir string `mapstructure:"-"` - Input conf.Input `mapstructure:"-"` } func (cfg *Config) Validate() error { diff --git a/internal/receiver/wineventlogreceiver/config.go b/internal/receiver/wineventlogreceiver/config.go index 80a6fc1..6c6b15a 100644 --- a/internal/receiver/wineventlogreceiver/config.go +++ b/internal/receiver/wineventlogreceiver/config.go @@ -8,9 +8,8 @@ import ( ) type Config struct { + Input conf.Input `mapstructure:"-"` + BaseDir string `mapstructure:"-"` Transforms []conf.Transform `mapstructure:"-"` Props []conf.Prop `mapstructure:"-"` - - BaseDir string `mapstructure:"-"` - Input conf.Input `mapstructure:"-"` }