Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
cb6f4ff
add precedence of configuration for splunk_outputs
omrozowicz-splunk Aug 17, 2026
15f9135
add precedence of configuration for splunk_inputs
omrozowicz-splunk Aug 17, 2026
07dcb0c
simplify, use the same filesystem for both inputs and outputs
omrozowicz-splunk Aug 17, 2026
26fd340
simplify hasScheme function
omrozowicz-splunk Aug 18, 2026
cecd7c1
draft of splunk_ta_observer
omrozowicz-splunk Aug 19, 2026
097c781
draft of splunk_ta_observer
omrozowicz-splunk Aug 19, 2026
cee0ee3
add watch observer under splunk inputs
omrozowicz-splunk Aug 20, 2026
da9a3a1
commit folderobserver
omrozowicz-splunk Aug 20, 2026
8958f8d
add logs
omrozowicz-splunk Aug 20, 2026
0c8f5f9
Merge remote-tracking branch 'origin/main' into splunk-inputs-precede…
omrozowicz-splunk Aug 20, 2026
dafa021
add tests
omrozowicz-splunk Aug 20, 2026
bd18256
delete stale code
omrozowicz-splunk Aug 20, 2026
42d130f
fix missing import
omrozowicz-splunk Aug 20, 2026
f84c499
fix problems after merge
omrozowicz-splunk Aug 20, 2026
32f51f6
add logging
omrozowicz-splunk Aug 20, 2026
bf0a4aa
change paths
omrozowicz-splunk Aug 20, 2026
36c2205
change receiver and subreceiver structure
omrozowicz-splunk Aug 21, 2026
ec69387
delete old testdata, change check for unsupported stanzas
omrozowicz-splunk Aug 21, 2026
e336161
add check for the directory if it is a TA
omrozowicz-splunk Aug 21, 2026
1d258ea
add check for the directory if it is a TA
omrozowicz-splunk Aug 21, 2026
156f584
add check for the directory if it is a TA
omrozowicz-splunk Aug 21, 2026
723e8a3
add check for script permissions
omrozowicz-splunk Aug 21, 2026
3e078c2
bring back functions refactor
omrozowicz-splunk Aug 21, 2026
02d1e0d
delete excessive logging
omrozowicz-splunk Aug 21, 2026
237cf67
change script path determining logic
omrozowicz-splunk Aug 21, 2026
da4618c
add function for checking if stanza is disabled
omrozowicz-splunk Aug 21, 2026
f92ad38
delete local folder observer
omrozowicz-splunk Aug 24, 2026
37f777c
delete baseDir parameter
omrozowicz-splunk Aug 24, 2026
4f7791d
fix tests
omrozowicz-splunk Aug 24, 2026
352e67c
bring back baseDir version
omrozowicz-splunk Aug 25, 2026
049a206
fix merge inputs
omrozowicz-splunk Aug 25, 2026
4a5c5c8
delete unnecessary functions
omrozowicz-splunk Aug 25, 2026
993b401
fix script intervals
omrozowicz-splunk Aug 26, 2026
cb11c5a
skip unkonwn stanzas
omrozowicz-splunk Aug 26, 2026
53ac7eb
bring back unknown stanza check
omrozowicz-splunk Aug 26, 2026
17dde90
bring back script float and script issue
omrozowicz-splunk Aug 26, 2026
55cfee6
Merge remote-tracking branch 'origin/main' into splunk-inputs-precede…
omrozowicz-splunk Aug 26, 2026
697e11a
merge main
omrozowicz-splunk Aug 26, 2026
2770687
rename to splunk_ta* and add tests
omrozowicz-splunk Aug 26, 2026
8fe7988
move testdata
omrozowicz-splunk Aug 26, 2026
3a1f957
go mod tidy
omrozowicz-splunk Aug 26, 2026
79ce235
handle unsupported stanza
omrozowicz-splunk Aug 26, 2026
b013240
add changelog
omrozowicz-splunk Aug 26, 2026
60245c9
minimise the comments
omrozowicz-splunk Aug 26, 2026
d577767
fix misleading comments
omrozowicz-splunk Aug 26, 2026
60522f9
move AppDir to input struct
omrozowicz-splunk Aug 27, 2026
cf4efb6
make ResolveSplunkHome global and use it in both inputs and outputs
omrozowicz-splunk Aug 27, 2026
ce601b6
run gofmt
omrozowicz-splunk Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .chloggen/splunk-inputs-precedence.yaml
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 4 additions & 3 deletions internal/collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
16 changes: 8 additions & 8 deletions internal/collector/collector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
})
Expand All @@ -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",
})
Expand Down Expand Up @@ -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",
})
Expand All @@ -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",
})
Expand All @@ -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",
})
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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",
})
Expand Down
47 changes: 46 additions & 1 deletion internal/conf/inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,17 @@ 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"`
}

func ReadInput(payload []byte) ([]Input, error) {
// IsDisabled reports whether the stanza has disabled=1.
func (s *Stanza) IsDisabled() bool {
p := s.Params.Get("disabled")
return p != nil && p.Value == "1"
}

func ReadInput(payload []byte, appDir string) ([]Input, error) {
f, err := ini.Load(payload)
if err != nil {
return nil, err
Expand All @@ -35,6 +42,7 @@ func ReadInput(payload []byte) ([]Input, error) {
continue // disregard default section. We need a stanza per input.
}
i := Input{
AppDir: appDir,
Configuration: Configuration{
Stanza: Stanza{
Name: section.Name(),
Expand All @@ -58,6 +66,43 @@ func ReadInput(payload []byte) ([]Input, error) {
return result, nil
}

// MergeInputs merges layered inputs; later layers take precedence per param key.
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] = mergeInput(result[idx], input)
} else {
seen[name] = len(result)
result = append(result, input)
}
}
}
return result
}

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
Expand Down
38 changes: 35 additions & 3 deletions internal/conf/inputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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: "",
Expand Down Expand Up @@ -112,6 +112,38 @@ 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 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 := `<?xml version="1.0" encoding="UTF-8"?>
<Input>
Expand All @@ -133,7 +165,7 @@ func TestToXML(t *testing.T) {
</Input>`
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"
Expand Down
20 changes: 20 additions & 0 deletions internal/conf/props.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 18 additions & 0 deletions internal/conf/transforms.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions internal/receiver/batchreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
}
5 changes: 2 additions & 3 deletions internal/receiver/monitorreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
}
2 changes: 1 addition & 1 deletion internal/receiver/scriptreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
}
5 changes: 2 additions & 3 deletions internal/receiver/tcpreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions internal/receiver/udpreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 2 additions & 3 deletions internal/receiver/wineventlogreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:"-"`
}
14 changes: 12 additions & 2 deletions internal/script/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,23 @@ func DetermineCommandName(baseDir string, input conf.Input) (string, error) {
if err != nil {
return "", err
}
resolveDir := baseDir
if input.AppDir != "" {
resolveDir = input.AppDir
}
switch parsed.Kind {
case "monitor", "batch":
return parsed.Target, nil
case "script":
return GetPath(baseDir, parsed.Target)
if filepath.IsAbs(parsed.Target) {
return parsed.Target, nil
}
return GetPath(resolveDir, parsed.Target)
case "":
return GetPath(baseDir, filepath.Join("bin", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), parsed.Target))
if filepath.IsAbs(parsed.Target) {
return parsed.Target, nil
}
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)
}
Expand Down
Loading
Loading