From 855c24352cf3f443aabf5d2131d4bc31acf7bb3d Mon Sep 17 00:00:00 2001 From: Tyler Helmuth <12352919+TylerHelmuth@users.noreply.github.com> Date: Tue, 29 Nov 2022 13:08:15 -0700 Subject: [PATCH] [pkg/ottl] Update IsMatch to convert bool int and float to strings (#16503) Updates IsMatch to intelligently convert bools, ints, and floats to strings before matching with the regex. This helps with IsMatch and attributes usage. Co-authored-by: Evan Bradley Co-authored-by: Bogdan Drutu --- .chloggen/ottl-improve-ismatch.yaml | 16 +++++++ pkg/ottl/go.mod | 2 +- pkg/ottl/ottlfuncs/README.md | 4 +- pkg/ottl/ottlfuncs/func_is_match.go | 37 ++++++++++++++-- pkg/ottl/ottlfuncs/func_is_match_test.go | 54 ++++++++++++++++++++---- 5 files changed, 98 insertions(+), 15 deletions(-) create mode 100755 .chloggen/ottl-improve-ismatch.yaml diff --git a/.chloggen/ottl-improve-ismatch.yaml b/.chloggen/ottl-improve-ismatch.yaml new file mode 100755 index 000000000000..800ff5bc89ee --- /dev/null +++ b/.chloggen/ottl-improve-ismatch.yaml @@ -0,0 +1,16 @@ +# 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: Updates the IsMatch function to convert bools, ints, and floats to strings before matching. + +# One or more tracking issues related to the change +issues: [16503] + +# (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: diff --git a/pkg/ottl/go.mod b/pkg/ottl/go.mod index ca466af0d035..72eca6443579 100644 --- a/pkg/ottl/go.mod +++ b/pkg/ottl/go.mod @@ -6,6 +6,7 @@ require ( github.com/alecthomas/participle/v2 v2.0.0-beta.5 github.com/gobwas/glob v0.2.3 github.com/iancoleman/strcase v0.2.0 + github.com/json-iterator/go v1.1.12 github.com/stretchr/testify v1.8.1 go.opentelemetry.io/collector/component v0.66.1-0.20221128222955-4ff1ff379b90 go.opentelemetry.io/collector/pdata v0.66.1-0.20221128222955-4ff1ff379b90 @@ -20,7 +21,6 @@ require ( github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/json-iterator/go v1.1.12 // indirect github.com/knadh/koanf v1.4.4 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect diff --git a/pkg/ottl/ottlfuncs/README.md b/pkg/ottl/ottlfuncs/README.md index 15943bc22582..f8433318b79f 100644 --- a/pkg/ottl/ottlfuncs/README.md +++ b/pkg/ottl/ottlfuncs/README.md @@ -118,7 +118,9 @@ The `IsMatch` factory function returns true if the `target` matches the regex `p `target` is either a path expression to a telemetry field to retrieve or a literal string. `pattern` is a regexp pattern. -The function matches the target against the pattern, returning true if the match is successful and false otherwise. If target is nil or not a string false is always returned. +The function matches the target against the pattern, returning true if the match is successful and false otherwise. +If target is a boolean, int, or float it will be converted to a string. +If target is nil or not a string, boolean, int, or float false is always returned. Examples: diff --git a/pkg/ottl/ottlfuncs/func_is_match.go b/pkg/ottl/ottlfuncs/func_is_match.go index dee38a110691..325d20a4a630 100644 --- a/pkg/ottl/ottlfuncs/func_is_match.go +++ b/pkg/ottl/ottlfuncs/func_is_match.go @@ -16,8 +16,14 @@ package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-c import ( "context" + "encoding/base64" + "errors" "fmt" "regexp" + "strconv" + + jsoniter "github.com/json-iterator/go" + "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) @@ -32,11 +38,34 @@ func IsMatch[K any](target ottl.Getter[K], pattern string) (ottl.ExprFunc[K], er if err != nil { return nil, err } - if val != nil { - if valStr, ok := val.(string); ok { - return compiledPattern.MatchString(valStr), nil + + switch v := val.(type) { + case string: + return compiledPattern.MatchString(v), nil + case bool: + return compiledPattern.MatchString(strconv.FormatBool(v)), nil + case int64: + return compiledPattern.MatchString(strconv.FormatInt(v, 10)), nil + case float64: + return compiledPattern.MatchString(strconv.FormatFloat(v, 'f', -1, 64)), nil + case []byte: + return compiledPattern.MatchString(base64.StdEncoding.EncodeToString(v)), nil + case pcommon.Map: + result, err := jsoniter.MarshalToString(v.AsRaw()) + if err != nil { + return nil, err + } + return compiledPattern.MatchString(result), nil + case pcommon.Slice: + result, err := jsoniter.MarshalToString(v.AsRaw()) + if err != nil { + return nil, err } + return compiledPattern.MatchString(result), nil + case pcommon.Value: + return compiledPattern.MatchString(v.AsString()), nil + default: + return nil, errors.New("unsupported type") } - return false, nil }, nil } diff --git a/pkg/ottl/ottlfuncs/func_is_match_test.go b/pkg/ottl/ottlfuncs/func_is_match_test.go index 4d91894445d1..2178e5e8793f 100644 --- a/pkg/ottl/ottlfuncs/func_is_match_test.go +++ b/pkg/ottl/ottlfuncs/func_is_match_test.go @@ -20,6 +20,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.opentelemetry.io/collector/pdata/pcommon" "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl" ) @@ -62,31 +63,53 @@ func Test_isMatch(t *testing.T) { expected: true, }, { - name: "target not a string", + name: "target bool", target: &ottl.StandardGetSetter[interface{}]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return 1, nil + return true, nil }, }, - pattern: "doesnt matter will be false", - expected: false, + pattern: "true", + expected: true, }, { - name: "target nil", + name: "target int", target: &ottl.StandardGetSetter[interface{}]{ Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { - return nil, nil + return int64(1), nil }, }, - pattern: "doesnt matter will be false", - expected: false, + pattern: `\d`, + expected: true, + }, + { + name: "target float", + target: &ottl.StandardGetSetter[interface{}]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + return 1.1, nil + }, + }, + pattern: `\d\.\d`, + expected: true, + }, + { + name: "target pcommon.Value", + target: &ottl.StandardGetSetter[interface{}]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + v := pcommon.NewValueEmpty() + v.SetStr("test") + return v, nil + }, + }, + pattern: `test`, + expected: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { exprFunc, err := IsMatch(tt.target, tt.pattern) assert.NoError(t, err) - result, err := exprFunc(nil, nil) + result, err := exprFunc(context.Background(), nil) assert.NoError(t, err) assert.Equal(t, tt.expected, result) }) @@ -102,3 +125,16 @@ func Test_isMatch_validation(t *testing.T) { _, err := IsMatch[interface{}](target, "\\K") require.Error(t, err) } + +func Test_isMatch_error(t *testing.T) { + target := &ottl.StandardGetSetter[interface{}]{ + Getter: func(ctx context.Context, tCtx interface{}) (interface{}, error) { + v := ottl.Path{} + return v, nil + }, + } + exprFunc, err := IsMatch[interface{}](target, "test") + assert.NoError(t, err) + _, err = exprFunc(context.Background(), nil) + require.Error(t, err) +}