diff --git a/.chloggen/ottl_function_changes.yaml b/.chloggen/ottl_function_changes.yaml new file mode 100755 index 000000000000..34bcede7f17b --- /dev/null +++ b/.chloggen/ottl_function_changes.yaml @@ -0,0 +1,25 @@ +# Use this changelog template to create an entry for release notes. +# If your change doesn't affect end users, such as a test fix or a tooling change, +# you should instead start your pull request title with [chore] or use the "Skip Changelog" label. + +# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix' +change_type: enhancement + +# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver) +component: pkg/ottl + +# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`). +note: Change replacement functions to accept a path expression as a replacement + +# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists. +issues: [22787] + +# (Optional) One or more lines of additional information to render under the primary note. +# These lines will be padded with 2 spaces and then inserted directly into the document. +# Use pipe (|) for multiline entries. +subtext: | + The following replacement functions now accept a path expression as a replacement: + - replace_match + - replace_pattern + - replace_all_matches + - replace_all_patterns diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index 1a2032e8d9fe..5eaf11ff0a10 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -148,7 +148,7 @@ Examples: The `replace_all_matches` function replaces any matching string value with the replacement string. -`target` is a path expression to a `pdata.Map` type field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). `replacement` is a string. +`target` is a path expression to a `pdata.Map` type field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). `replacement` is either a path expression to a string telemetry field or a literal string. Each string value in `target` that matches `pattern` will get replaced with `replacement`. Non-string values are ignored. @@ -162,7 +162,7 @@ Examples: The `replace_all_patterns` function replaces any segments in a string value or key that match the regex pattern with the replacement string. -`target` is a path expression to a `pdata.Map` type field. `regex` is a regex string indicating a segment to replace. `replacement` is a string. +`target` is a path expression to a `pdata.Map` type field. `regex` is a regex string indicating a segment to replace. `replacement` is either a path expression to a string telemetry field or a literal string. `mode` determines whether the match and replace will occur on the map's value or key. Valid values are `key` and `value`. @@ -186,7 +186,7 @@ If using OTTL outside of collector configuration, `$` should not be escaped and The `replace_match` function allows replacing entire strings if they match a glob pattern. -`target` is a path expression to a telemetry field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). `replacement` is a string. +`target` is a path expression to a telemetry field. `pattern` is a string following [filepath.Match syntax](https://pkg.go.dev/path/filepath#Match). `replacement` is either a path expression to a string telemetry field or a literal string. If `target` matches `pattern` it will get replaced with `replacement`. @@ -200,7 +200,7 @@ Examples: The `replace_pattern` function allows replacing all string sections that match a regex pattern with a new value. -`target` is a path expression to a telemetry field. `regex` is a regex string indicating a segment to replace. `replacement` is a string. +`target` is a path expression to a telemetry field. `regex` is a regex string indicating a segment to replace. `replacement` is either a path expression to a string telemetry field or a literal string. If one or more sections of `target` match `regex` they will get replaced with `replacement`. diff --git a/pkg/ottl/ottlfuncs/func_replace_all_matches.go b/pkg/ottl/ottlfuncs/func_replace_all_matches.go index 275e1566b090..457a4582477c 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_matches.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_matches.go @@ -14,9 +14,9 @@ import ( ) type ReplaceAllMatchesArguments[K any] struct { - Target ottl.PMapGetter[K] `ottlarg:"0"` - Pattern string `ottlarg:"1"` - Replacement string `ottlarg:"2"` + Target ottl.PMapGetter[K] `ottlarg:"0"` + Pattern string `ottlarg:"1"` + Replacement ottl.StringGetter[K] `ottlarg:"2"` } func NewReplaceAllMatchesFactory[K any]() ottl.Factory[K] { @@ -33,7 +33,7 @@ func createReplaceAllMatchesFunction[K any](_ ottl.FunctionContext, oArgs ottl.A return replaceAllMatches(args.Target, args.Pattern, args.Replacement) } -func replaceAllMatches[K any](target ottl.PMapGetter[K], pattern string, replacement string) (ottl.ExprFunc[K], error) { +func replaceAllMatches[K any](target ottl.PMapGetter[K], pattern string, replacement ottl.StringGetter[K]) (ottl.ExprFunc[K], error) { glob, err := glob.Compile(pattern) if err != nil { return nil, fmt.Errorf("the pattern supplied to replace_match is not a valid pattern: %w", err) @@ -43,9 +43,13 @@ func replaceAllMatches[K any](target ottl.PMapGetter[K], pattern string, replace if err != nil { return nil, err } + replacementVal, err := replacement.Get(ctx, tCtx) + if err != nil { + return nil, err + } val.Range(func(key string, value pcommon.Value) bool { if glob.Match(value.Str()) { - value.SetStr(replacement) + value.SetStr(replacementVal) } return true }) diff --git a/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go b/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go index ff993737835d..d2690bb1c1d3 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_matches_test.go @@ -29,14 +29,18 @@ func Test_replaceAllMatches(t *testing.T) { name string target ottl.PMapGetter[pcommon.Map] pattern string - replacement string + replacement ottl.StringGetter[pcommon.Map] want func(pcommon.Map) }{ { - name: "replace only matches", - target: target, - pattern: "hello*", - replacement: "hello {universe}", + name: "replace only matches", + target: target, + pattern: "hello*", + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "hello {universe}", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.PutStr("test", "hello {universe}") expectedMap.PutStr("test2", "hello {universe}") @@ -44,10 +48,14 @@ func Test_replaceAllMatches(t *testing.T) { }, }, { - name: "no matches", - target: target, - pattern: "nothing*", - replacement: "nothing {matches}", + name: "no matches", + target: target, + pattern: "nothing*", + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "nothing {matches}", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.PutStr("test", "hello world") expectedMap.PutStr("test2", "hello") @@ -82,8 +90,13 @@ func Test_replaceAllMatches_bad_input(t *testing.T) { return tCtx, nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{replacement}", nil + }, + } - exprFunc, err := replaceAllMatches[interface{}](target, "*", "{replacement}") + exprFunc, err := replaceAllMatches[interface{}](target, "*", replacement) assert.NoError(t, err) _, err = exprFunc(nil, input) assert.Error(t, err) @@ -95,8 +108,13 @@ func Test_replaceAllMatches_get_nil(t *testing.T) { return tCtx, nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{anything}", nil + }, + } - exprFunc, err := replaceAllMatches[interface{}](target, "*", "{anything}") + exprFunc, err := replaceAllMatches[interface{}](target, "*", replacement) assert.NoError(t, err) _, err = exprFunc(nil, nil) assert.Error(t, err) diff --git a/pkg/ottl/ottlfuncs/func_replace_all_patterns.go b/pkg/ottl/ottlfuncs/func_replace_all_patterns.go index 7e879e420ee4..f4d9044c93a5 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_patterns.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_patterns.go @@ -19,10 +19,10 @@ const ( ) type ReplaceAllPatternsArguments[K any] struct { - Target ottl.PMapGetter[K] `ottlarg:"0"` - Mode string `ottlarg:"1"` - RegexPattern string `ottlarg:"2"` - Replacement string `ottlarg:"3"` + Target ottl.PMapGetter[K] `ottlarg:"0"` + Mode string `ottlarg:"1"` + RegexPattern string `ottlarg:"2"` + Replacement ottl.StringGetter[K] `ottlarg:"3"` } func NewReplaceAllPatternsFactory[K any]() ottl.Factory[K] { @@ -39,7 +39,7 @@ func createReplaceAllPatternsFunction[K any](_ ottl.FunctionContext, oArgs ottl. return replaceAllPatterns(args.Target, args.Mode, args.RegexPattern, args.Replacement) } -func replaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPattern string, replacement string) (ottl.ExprFunc[K], error) { +func replaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPattern string, replacement ottl.StringGetter[K]) (ottl.ExprFunc[K], error) { compiledPattern, err := regexp.Compile(regexPattern) if err != nil { return nil, fmt.Errorf("the regex pattern supplied to replace_all_patterns is not a valid pattern: %w", err) @@ -53,20 +53,24 @@ func replaceAllPatterns[K any](target ottl.PMapGetter[K], mode string, regexPatt if err != nil { return nil, err } + replacementVal, err := replacement.Get(ctx, tCtx) + if err != nil { + return nil, err + } updated := pcommon.NewMap() updated.EnsureCapacity(val.Len()) val.Range(func(key string, originalValue pcommon.Value) bool { switch mode { case modeValue: if compiledPattern.MatchString(originalValue.Str()) { - updatedString := compiledPattern.ReplaceAllString(originalValue.Str(), replacement) + updatedString := compiledPattern.ReplaceAllString(originalValue.Str(), replacementVal) updated.PutStr(key, updatedString) } else { originalValue.CopyTo(updated.PutEmpty(key)) } case modeKey: if compiledPattern.MatchString(key) { - updatedKey := compiledPattern.ReplaceAllString(key, replacement) + updatedKey := compiledPattern.ReplaceAllString(key, replacementVal) originalValue.CopyTo(updated.PutEmpty(updatedKey)) } else { originalValue.CopyTo(updated.PutEmpty(key)) diff --git a/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go b/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go index c37dc6605b7c..58ad07893d6f 100644 --- a/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_all_patterns_test.go @@ -34,15 +34,19 @@ func Test_replaceAllPatterns(t *testing.T) { target ottl.PMapGetter[pcommon.Map] mode string pattern string - replacement string + replacement ottl.StringGetter[pcommon.Map] want func(pcommon.Map) }{ { - name: "replace only matches", - target: target, - mode: modeValue, - pattern: "hello", - replacement: "hello {universe}", + name: "replace only matches", + target: target, + mode: modeValue, + pattern: "hello", + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "hello {universe}", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.PutStr("test", "hello {universe} world") expectedMap.PutStr("test2", "hello {universe}") @@ -53,11 +57,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "no matches", - target: target, - mode: modeValue, - pattern: "nothing", - replacement: "nothing {matches}", + name: "no matches", + target: target, + mode: modeValue, + pattern: "nothing", + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "nothing {matches}", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.PutStr("test", "hello world") expectedMap.PutStr("test2", "hello") @@ -68,11 +76,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "multiple regex match", - target: target, - mode: modeValue, - pattern: `world[^\s]*(\s?)`, - replacement: "**** ", + name: "multiple regex match", + target: target, + mode: modeValue, + pattern: `world[^\s]*(\s?)`, + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "**** ", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.PutStr("test", "hello **** ") expectedMap.PutStr("test2", "hello") @@ -83,11 +95,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "replace only matches", - target: target, - mode: modeKey, - pattern: "test2", - replacement: "foo", + name: "replace only matches", + target: target, + mode: modeKey, + pattern: "test2", + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "foo", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.Clear() expectedMap.PutStr("test", "hello world") @@ -99,11 +115,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "no matches", - target: target, - mode: modeKey, - pattern: "nothing", - replacement: "nothing {matches}", + name: "no matches", + target: target, + mode: modeKey, + pattern: "nothing", + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "nothing {matches}", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.Clear() expectedMap.PutStr("test", "hello world") @@ -115,11 +135,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "multiple regex match", - target: target, - mode: modeKey, - pattern: `test`, - replacement: "test.", + name: "multiple regex match", + target: target, + mode: modeKey, + pattern: `test`, + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "test.", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.Clear() expectedMap.PutStr("test.", "hello world") @@ -131,11 +155,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "expand capturing groups in values", - target: target, - mode: modeValue, - pattern: `world(\d)`, - replacement: "world-$1", + name: "expand capturing groups in values", + target: target, + mode: modeValue, + pattern: `world(\d)`, + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "world-$1", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.Clear() expectedMap.PutStr("test", "hello world") @@ -147,11 +175,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "expand capturing groups in keys", - target: target, - mode: modeKey, - pattern: `test(\d)`, - replacement: "test-$1", + name: "expand capturing groups in keys", + target: target, + mode: modeKey, + pattern: `test(\d)`, + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "test-$1", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.PutStr("test", "hello world") expectedMap.PutStr("test-2", "hello") @@ -162,11 +194,15 @@ func Test_replaceAllPatterns(t *testing.T) { }, }, { - name: "replacement with literal $", - target: target, - mode: modeValue, - pattern: `world(\d)`, - replacement: "$$world-$1", + name: "replacement with literal $", + target: target, + mode: modeValue, + pattern: `world(\d)`, + replacement: ottl.StandardStringGetter[pcommon.Map]{ + Getter: func(context.Context, pcommon.Map) (interface{}, error) { + return "$$world-$1", nil + }, + }, want: func(expectedMap pcommon.Map) { expectedMap.Clear() expectedMap.PutStr("test", "hello world") @@ -205,8 +241,13 @@ func Test_replaceAllPatterns_bad_input(t *testing.T) { return tCtx, nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{replacement}", nil + }, + } - exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, "regexpattern", "{replacement}") + exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, "regexpattern", replacement) assert.Nil(t, err) _, err = exprFunc(nil, input) @@ -219,8 +260,13 @@ func Test_replaceAllPatterns_get_nil(t *testing.T) { return tCtx, nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{anything}", nil + }, + } - exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, "regexp", "{anything}") + exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, "regexp", replacement) assert.NoError(t, err) _, err = exprFunc(nil, nil) @@ -234,9 +280,14 @@ func Test_replaceAllPatterns_invalid_pattern(t *testing.T) { return nil, nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{anything}", nil + }, + } invalidRegexPattern := "*" - exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, invalidRegexPattern, "{anything}") + exprFunc, err := replaceAllPatterns[interface{}](target, modeValue, invalidRegexPattern, replacement) require.Error(t, err) assert.ErrorContains(t, err, "error parsing regexp:") assert.Nil(t, exprFunc) @@ -249,9 +300,14 @@ func Test_replaceAllPatterns_invalid_model(t *testing.T) { return nil, nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{anything}", nil + }, + } invalidMode := "invalid" - exprFunc, err := replaceAllPatterns[interface{}](target, invalidMode, "regex", "{anything}") + exprFunc, err := replaceAllPatterns[interface{}](target, invalidMode, "regex", replacement) assert.Nil(t, exprFunc) assert.Contains(t, err.Error(), "invalid mode") } diff --git a/pkg/ottl/ottlfuncs/func_replace_match.go b/pkg/ottl/ottlfuncs/func_replace_match.go index ebab2bd3283f..3e15bb87c849 100644 --- a/pkg/ottl/ottlfuncs/func_replace_match.go +++ b/pkg/ottl/ottlfuncs/func_replace_match.go @@ -13,9 +13,9 @@ import ( ) type ReplaceMatchArguments[K any] struct { - Target ottl.GetSetter[K] `ottlarg:"0"` - Pattern string `ottlarg:"1"` - Replacement string `ottlarg:"2"` + Target ottl.GetSetter[K] `ottlarg:"0"` + Pattern string `ottlarg:"1"` + Replacement ottl.StringGetter[K] `ottlarg:"2"` } func NewReplaceMatchFactory[K any]() ottl.Factory[K] { @@ -32,7 +32,7 @@ func createReplaceMatchFunction[K any](_ ottl.FunctionContext, oArgs ottl.Argume return replaceMatch(args.Target, args.Pattern, args.Replacement) } -func replaceMatch[K any](target ottl.GetSetter[K], pattern string, replacement string) (ottl.ExprFunc[K], error) { +func replaceMatch[K any](target ottl.GetSetter[K], pattern string, replacement ottl.StringGetter[K]) (ottl.ExprFunc[K], error) { glob, err := glob.Compile(pattern) if err != nil { return nil, fmt.Errorf("the pattern supplied to replace_match is not a valid pattern: %w", err) @@ -42,12 +42,16 @@ func replaceMatch[K any](target ottl.GetSetter[K], pattern string, replacement s if err != nil { return nil, err } + replacementVal, err := replacement.Get(ctx, tCtx) + if err != nil { + return nil, err + } if val == nil { return nil, nil } if valStr, ok := val.(string); ok { if glob.Match(valStr) { - err = target.Set(ctx, tCtx, replacement) + err = target.Set(ctx, tCtx, replacementVal) if err != nil { return nil, err } diff --git a/pkg/ottl/ottlfuncs/func_replace_match_test.go b/pkg/ottl/ottlfuncs/func_replace_match_test.go index 2108d87ae75b..b2b726bf51fd 100644 --- a/pkg/ottl/ottlfuncs/func_replace_match_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_match_test.go @@ -30,23 +30,31 @@ func Test_replaceMatch(t *testing.T) { name string target ottl.GetSetter[pcommon.Value] pattern string - replacement string + replacement ottl.StringGetter[pcommon.Value] want func(pcommon.Value) }{ { - name: "replace match", - target: target, - pattern: "hello*", - replacement: "hello {universe}", + name: "replace match", + target: target, + pattern: "hello*", + replacement: ottl.StandardStringGetter[pcommon.Value]{ + Getter: func(context.Context, pcommon.Value) (interface{}, error) { + return "hello {universe}", nil + }, + }, want: func(expectedValue pcommon.Value) { expectedValue.SetStr("hello {universe}") }, }, { - name: "no match", - target: target, - pattern: "goodbye*", - replacement: "goodbye {universe}", + name: "no match", + target: target, + pattern: "goodbye*", + replacement: ottl.StandardStringGetter[pcommon.Value]{ + Getter: func(context.Context, pcommon.Value) (interface{}, error) { + return "goodbye {universe}", nil + }, + }, want: func(expectedValue pcommon.Value) { expectedValue.SetStr("hello world") }, @@ -81,8 +89,13 @@ func Test_replaceMatch_bad_input(t *testing.T) { return nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{replacement}", nil + }, + } - exprFunc, err := replaceMatch[interface{}](target, "*", "{replacement}") + exprFunc, err := replaceMatch[interface{}](target, "*", replacement) assert.NoError(t, err) result, err := exprFunc(nil, input) @@ -102,8 +115,13 @@ func Test_replaceMatch_get_nil(t *testing.T) { return nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{anything}", nil + }, + } - exprFunc, err := replaceMatch[interface{}](target, "*", "{anything}") + exprFunc, err := replaceMatch[interface{}](target, "*", replacement) assert.NoError(t, err) result, err := exprFunc(nil, nil) diff --git a/pkg/ottl/ottlfuncs/func_replace_pattern.go b/pkg/ottl/ottlfuncs/func_replace_pattern.go index 9ff3b76070f8..cd5139d1c514 100644 --- a/pkg/ottl/ottlfuncs/func_replace_pattern.go +++ b/pkg/ottl/ottlfuncs/func_replace_pattern.go @@ -12,9 +12,9 @@ import ( ) type ReplacePatternArguments[K any] struct { - Target ottl.GetSetter[K] `ottlarg:"0"` - RegexPattern string `ottlarg:"1"` - Replacement string `ottlarg:"2"` + Target ottl.GetSetter[K] `ottlarg:"0"` + RegexPattern string `ottlarg:"1"` + Replacement ottl.StringGetter[K] `ottlarg:"2"` } func NewReplacePatternFactory[K any]() ottl.Factory[K] { @@ -31,7 +31,7 @@ func createReplacePatternFunction[K any](_ ottl.FunctionContext, oArgs ottl.Argu return replacePattern(args.Target, args.RegexPattern, args.Replacement) } -func replacePattern[K any](target ottl.GetSetter[K], regexPattern string, replacement string) (ottl.ExprFunc[K], error) { +func replacePattern[K any](target ottl.GetSetter[K], regexPattern string, replacement ottl.StringGetter[K]) (ottl.ExprFunc[K], error) { compiledPattern, err := regexp.Compile(regexPattern) if err != nil { return nil, fmt.Errorf("the regex pattern supplied to replace_pattern is not a valid pattern: %w", err) @@ -41,12 +41,16 @@ func replacePattern[K any](target ottl.GetSetter[K], regexPattern string, replac if err != nil { return nil, err } + replacementVal, err := replacement.Get(ctx, tCtx) + if err != nil { + return nil, err + } if originalVal == nil { return nil, nil } if originalValStr, ok := originalVal.(string); ok { if compiledPattern.MatchString(originalValStr) { - updatedStr := compiledPattern.ReplaceAllString(originalValStr, replacement) + updatedStr := compiledPattern.ReplaceAllString(originalValStr, replacementVal) err = target.Set(ctx, tCtx, updatedStr) if err != nil { return nil, err diff --git a/pkg/ottl/ottlfuncs/func_replace_pattern_test.go b/pkg/ottl/ottlfuncs/func_replace_pattern_test.go index 19427e8d0ecf..278cc2ccbb57 100644 --- a/pkg/ottl/ottlfuncs/func_replace_pattern_test.go +++ b/pkg/ottl/ottlfuncs/func_replace_pattern_test.go @@ -31,50 +31,70 @@ func Test_replacePattern(t *testing.T) { name string target ottl.GetSetter[pcommon.Value] pattern string - replacement string + replacement ottl.StringGetter[pcommon.Value] want func(pcommon.Value) }{ { - name: "replace regex match", - target: target, - pattern: `passwd\=[^\s]*(\s?)`, - replacement: "passwd=*** ", + name: "replace regex match", + target: target, + pattern: `passwd\=[^\s]*(\s?)`, + replacement: ottl.StandardStringGetter[pcommon.Value]{ + Getter: func(context.Context, pcommon.Value) (interface{}, error) { + return "passwd=*** ", nil + }, + }, want: func(expectedValue pcommon.Value) { expectedValue.SetStr("application passwd=*** otherarg=notsensitive key1 key2") }, }, { - name: "no regex match", - target: target, - pattern: `nomatch\=[^\s]*(\s?)`, - replacement: "shouldnotbeinoutput", + name: "no regex match", + target: target, + pattern: `nomatch\=[^\s]*(\s?)`, + replacement: ottl.StandardStringGetter[pcommon.Value]{ + Getter: func(context.Context, pcommon.Value) (interface{}, error) { + return "shouldnotbeinoutput", nil + }, + }, want: func(expectedValue pcommon.Value) { expectedValue.SetStr("application passwd=sensitivedtata otherarg=notsensitive key1 key2") }, }, { - name: "multiple regex match", - target: target, - pattern: `key[^\s]*(\s?)`, - replacement: "**** ", + name: "multiple regex match", + target: target, + pattern: `key[^\s]*(\s?)`, + replacement: ottl.StandardStringGetter[pcommon.Value]{ + Getter: func(context.Context, pcommon.Value) (interface{}, error) { + return "**** ", nil + }, + }, want: func(expectedValue pcommon.Value) { expectedValue.SetStr("application passwd=sensitivedtata otherarg=notsensitive **** **** ") }, }, { - name: "expand capturing groups", - target: target, - pattern: `(\w+)=(\w+)`, - replacement: "$1:$2", + name: "expand capturing groups", + target: target, + pattern: `(\w+)=(\w+)`, + replacement: ottl.StandardStringGetter[pcommon.Value]{ + Getter: func(context.Context, pcommon.Value) (interface{}, error) { + return "$1:$2", nil + }, + }, want: func(expectedValue pcommon.Value) { expectedValue.SetStr("application passwd:sensitivedtata otherarg:notsensitive key1 key2") }, }, { - name: "replacement with literal $", - target: target, - pattern: `passwd\=[^\s]*(\s?)`, - replacement: "passwd=$$$$$$ ", + name: "replacement with literal $", + target: target, + pattern: `passwd\=[^\s]*(\s?)`, + replacement: ottl.StandardStringGetter[pcommon.Value]{ + Getter: func(context.Context, pcommon.Value) (interface{}, error) { + return "passwd=$$$$$$ ", nil + }, + }, want: func(expectedValue pcommon.Value) { expectedValue.SetStr("application passwd=$$$ otherarg=notsensitive key1 key2") }, @@ -83,7 +103,6 @@ func Test_replacePattern(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { scenarioValue := pcommon.NewValueStr(input.Str()) - exprFunc, err := replacePattern(tt.target, tt.pattern, tt.replacement) assert.NoError(t, err) @@ -110,8 +129,13 @@ func Test_replacePattern_bad_input(t *testing.T) { return nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{replacement}", nil + }, + } - exprFunc, err := replacePattern[interface{}](target, "regexp", "{replacement}") + exprFunc, err := replacePattern[interface{}](target, "regexp", replacement) assert.NoError(t, err) result, err := exprFunc(nil, input) @@ -130,8 +154,13 @@ func Test_replacePattern_get_nil(t *testing.T) { return nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{anything}", nil + }, + } - exprFunc, err := replacePattern[interface{}](target, `nomatch\=[^\s]*(\s?)`, "{anything}") + exprFunc, err := replacePattern[interface{}](target, `nomatch\=[^\s]*(\s?)`, replacement) assert.NoError(t, err) result, err := exprFunc(nil, nil) @@ -150,9 +179,14 @@ func Test_replacePatterns_invalid_pattern(t *testing.T) { return nil }, } + replacement := &ottl.StandardStringGetter[interface{}]{ + Getter: func(context.Context, interface{}) (interface{}, error) { + return "{anything}", nil + }, + } invalidRegexPattern := "*" - _, err := replacePattern[interface{}](target, invalidRegexPattern, "{anything}") + _, err := replacePattern[interface{}](target, invalidRegexPattern, replacement) require.Error(t, err) assert.ErrorContains(t, err, "error parsing regexp:") }