Skip to content

Commit

Permalink
Add IsMatchVersion convertor to pkg/ottl.
Browse files Browse the repository at this point in the history
  • Loading branch information
boh-dan committed Aug 13, 2024
1 parent 55fdd84 commit 322af7e
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 0 deletions.
27 changes: 27 additions & 0 deletions .chloggen/ottl_is_match_version.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#Use this changelog template to create an entry for release notes.

# 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: Introduce IsMatchVersion converter

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: []

# (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:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
14 changes: 14 additions & 0 deletions pkg/ottl/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,19 @@ func Test_e2e_converters(t *testing.T) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
statement: `set(attributes["test"], "pass") where IsMatchVersion("1.2.3", "1.2.x")`,
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},
{
statement: `set(attributes["test"], "pass") where IsMatchVersion(resource.attributes["app.version"], "1.2.x")`,
want: func(tCtx ottllog.TransformContext) {
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
},
},

{
statement: `set(attributes["test"], "pass") where IsString("")`,
want: func(tCtx ottllog.TransformContext) {
Expand Down Expand Up @@ -938,6 +951,7 @@ func Test_ProcessTraces_TraceContext(t *testing.T) {
func constructLogTransformContext() ottllog.TransformContext {
resource := pcommon.NewResource()
resource.Attributes().PutStr("host.name", "localhost")
resource.Attributes().PutStr("app.version", "1.2.3")

scope := pcommon.NewInstrumentationScope()
scope.SetName("scope")
Expand Down
1 change: 1 addition & 0 deletions pkg/ottl/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl
go 1.21.0

require (
github.com/Masterminds/semver/v3 v3.2.1
github.com/alecthomas/participle/v2 v2.1.1
github.com/elastic/go-grok v0.3.1
github.com/gobwas/glob v0.2.3
Expand Down
2 changes: 2 additions & 0 deletions pkg/ottl/go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

76 changes: 76 additions & 0 deletions pkg/ottl/ottlfuncs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,7 @@ Available Converters:
- [IsRootSpan](#isrootspan)
- [IsMap](#ismap)
- [IsMatch](#ismatch)
- [IsMatchVersion](#ismatchversion)
- [IsList](#islist)
- [IsString](#isstring)
- [Len](#len)
Expand Down Expand Up @@ -919,6 +920,81 @@ Examples:

- `IsMatch("string", ".*ring")`

### IsMatchVersion

`IsMatchVersion(target, constraint)`

The `IsMatchVersion` Converter returns true if the `target` contains valid semver version and match `constraint`.
`target` is either a path expression to a telemetry field to retrieve or a literal string. `constraint` is an expration describing a range of allowed versions.

The function matches the target against the contstrain, returning true if the target contains a valid semver version and satisfy the `constraint`.
The target is expected to be a string and value should be a valid semver version otherwise returned value will be false.
If target is nil, false is always returned.


This converter is based on [a semver library](https://github.com/Masterminds/semver) that supports following version comparisons (taken from the official README.md):

#### Hyphen Range Comparisons

There are multiple methods to handle ranges and the first is hyphens ranges.
These look like:

* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5`
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5`

Note that `1.2-1.4.5` without whitespace is parsed completely differently; it's
parsed as a single constraint `1.2.0` with _prerelease_ `1.4.5`.

#### Wildcards In Comparisons

The `x`, `X`, and `*` characters can be used as a wildcard character. This works
for all comparison operators. When used on the `=` operator it falls
back to the patch level comparison (see tilde below). For example,

* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
* `>= 1.2.x` is equivalent to `>= 1.2.0`
* `<= 2.x` is equivalent to `< 3`
* `*` is equivalent to `>= 0.0.0`

#### Tilde Range Comparisons (Patch)

The tilde (`~`) comparison operator is for patch level ranges when a minor
version is specified and major level changes when the minor number is missing.
For example,

* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
* `~1` is equivalent to `>= 1, < 2`
* `~2.3` is equivalent to `>= 2.3, < 2.4`
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
* `~1.x` is equivalent to `>= 1, < 2`

#### Caret Range Comparisons (Major)

The caret (`^`) comparison operator is for major level changes once a stable
(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts
as the API stability level. This is useful when comparisons of API versions as a
major change is API breaking. For example,

* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
* `^2.3` is equivalent to `>= 2.3, < 3`
* `^2.x` is equivalent to `>= 2.0.0, < 3`
* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0`
* `^0.2` is equivalent to `>=0.2.0 <0.3.0`
* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4`
* `^0.0` is equivalent to `>=0.0.0 <0.1.0`
* `^0` is equivalent to `>=0.0.0 <1.0.0`


Examples:

- `IsMatchVersion(resource.attributes["app.version"], "1.2.x")`

- `IsMatchVersion("1.2.3", "~1.2")`

- `IsMatchVersion(attributes["version"], "1.2.0-1.2.5")`


### IsList

`IsList(value)`
Expand Down
54 changes: 54 additions & 0 deletions pkg/ottl/ottlfuncs/func_is_match_version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs // import "github.com/canva/otel-platform/opentelemetry-collector/pkg/ottl/ottlfuncs"

import (
"context"
"fmt"

"github.com/Masterminds/semver/v3"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

type IsMatchVersionArguments[K any] struct {
Target ottl.StringGetter[K]
Constraint string
}

func NewIsMatchVersionFactory[K any]() ottl.Factory[K] {
return ottl.NewFactory("IsMatchVersion", &IsMatchVersionArguments[K]{}, createIsMatchVersionFunction[K])
}

func createIsMatchVersionFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
args, ok := oArgs.(*IsMatchVersionArguments[K])

if !ok {
return nil, fmt.Errorf("IsMatchVersionFactory args must be of type *IsMatchVersionArguments[K]")
}

return isMatchVersion(args.Target, args.Constraint)
}

func isMatchVersion[K any](target ottl.StringGetter[K], constraint string) (ottl.ExprFunc[K], error) {
semverconstraint, err := semver.NewConstraint(constraint)
if err != nil {
return nil, fmt.Errorf("the constrain supplied to IsMatchVersion is not a valid: %w", err)
}
return func(ctx context.Context, tCtx K) (any, error) {
val, err := target.Get(ctx, tCtx)

if err != nil {
return false, err
}

version, err := semver.NewVersion(val)

if err != nil {
return false, fmt.Errorf("failed to parse semver version from: %s, err: %w", val, err)
}

return semverconstraint.Check(version), nil
}, nil
}
98 changes: 98 additions & 0 deletions pkg/ottl/ottlfuncs/func_is_match_version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs

import (
"context"
"testing"

"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"

"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
)

func Test_isMatchVersion(t *testing.T) {
tests := []struct {
name string
value any
constraint string
expected bool
wantErr bool
}{
{
name: "version match",
value: "1.2.3",
constraint: "1.2.x",
expected: true,
wantErr: false,
},
{
name: "version doesn't match",
value: "1.3.3",
constraint: "1.2.x",
expected: false,
wantErr: false,
},
{
name: "version pcommon.ValueTypeStr",
value: pcommon.NewValueStr("1.2.3"),
constraint: "1.2.x",
expected: true,
wantErr: false,
},
{
name: "version pcommon.ValueTypeInt",
value: pcommon.NewValueInt(123),
constraint: "1.2.x",
expected: false,
wantErr: true,
},
{
name: "not valid version string type",
value: "abc.2.3",
constraint: "1.2.x",
expected: false,
wantErr: true,
},
{
name: "nil value",
value: nil,
constraint: "1.2.x",
expected: false,
wantErr: true,
},
{
name: "not valid version pcommon.ValueTypeStr",
value: pcommon.NewValueStr("abc.2.3"),
constraint: "1.2.x",
expected: false,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
exprFunc, err := isMatchVersion[any](&ottl.StandardStringGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return tt.value, nil
},
}, tt.constraint)
assert.NoError(t, err)
result, err := exprFunc(context.Background(), nil)

assert.True(t, (err != nil) == tt.wantErr, "Expected errors: %t received error: %t, err: %w", tt.wantErr, err != nil, err)
assert.Equal(t, tt.expected, result)
})
}
}

func Test_isMatchVersion_invalid_constrain(t *testing.T) {
target := &ottl.StandardStringGetter[any]{
Getter: func(_ context.Context, _ any) (any, error) {
return "1.2.3", nil
},
}
_, err := isMatchVersion[any](target, "abc.1.2")
assert.Error(t, err)
}
1 change: 1 addition & 0 deletions pkg/ottl/ottlfuncs/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,6 @@ func converters[K any]() []ottl.Factory[K] {
NewAppendFactory[K](),
NewYearFactory[K](),
NewHexFactory[K](),
NewIsMatchVersionFactory[K](),
}
}

0 comments on commit 322af7e

Please sign in to comment.