Skip to content

Commit 322af7e

Browse files
committed
Add IsMatchVersion convertor to pkg/ottl.
1 parent 55fdd84 commit 322af7e

File tree

8 files changed

+273
-0
lines changed

8 files changed

+273
-0
lines changed

.chloggen/ottl_is_match_version.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: pkg/ottl
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Introduce IsMatchVersion converter
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: []
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

pkg/ottl/e2e/e2e_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,19 @@ func Test_e2e_converters(t *testing.T) {
492492
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
493493
},
494494
},
495+
{
496+
statement: `set(attributes["test"], "pass") where IsMatchVersion("1.2.3", "1.2.x")`,
497+
want: func(tCtx ottllog.TransformContext) {
498+
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
499+
},
500+
},
501+
{
502+
statement: `set(attributes["test"], "pass") where IsMatchVersion(resource.attributes["app.version"], "1.2.x")`,
503+
want: func(tCtx ottllog.TransformContext) {
504+
tCtx.GetLogRecord().Attributes().PutStr("test", "pass")
505+
},
506+
},
507+
495508
{
496509
statement: `set(attributes["test"], "pass") where IsString("")`,
497510
want: func(tCtx ottllog.TransformContext) {
@@ -938,6 +951,7 @@ func Test_ProcessTraces_TraceContext(t *testing.T) {
938951
func constructLogTransformContext() ottllog.TransformContext {
939952
resource := pcommon.NewResource()
940953
resource.Attributes().PutStr("host.name", "localhost")
954+
resource.Attributes().PutStr("app.version", "1.2.3")
941955

942956
scope := pcommon.NewInstrumentationScope()
943957
scope.SetName("scope")

pkg/ottl/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl
33
go 1.21.0
44

55
require (
6+
github.com/Masterminds/semver/v3 v3.2.1
67
github.com/alecthomas/participle/v2 v2.1.1
78
github.com/elastic/go-grok v0.3.1
89
github.com/gobwas/glob v0.2.3

pkg/ottl/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/ottl/ottlfuncs/README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ Available Converters:
429429
- [IsRootSpan](#isrootspan)
430430
- [IsMap](#ismap)
431431
- [IsMatch](#ismatch)
432+
- [IsMatchVersion](#ismatchversion)
432433
- [IsList](#islist)
433434
- [IsString](#isstring)
434435
- [Len](#len)
@@ -919,6 +920,81 @@ Examples:
919920

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

923+
### IsMatchVersion
924+
925+
`IsMatchVersion(target, constraint)`
926+
927+
The `IsMatchVersion` Converter returns true if the `target` contains valid semver version and match `constraint`.
928+
`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.
929+
930+
The function matches the target against the contstrain, returning true if the target contains a valid semver version and satisfy the `constraint`.
931+
The target is expected to be a string and value should be a valid semver version otherwise returned value will be false.
932+
If target is nil, false is always returned.
933+
934+
935+
This converter is based on [a semver library](https://github.com/Masterminds/semver) that supports following version comparisons (taken from the official README.md):
936+
937+
#### Hyphen Range Comparisons
938+
939+
There are multiple methods to handle ranges and the first is hyphens ranges.
940+
These look like:
941+
942+
* `1.2 - 1.4.5` which is equivalent to `>= 1.2 <= 1.4.5`
943+
* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4 <= 4.5`
944+
945+
Note that `1.2-1.4.5` without whitespace is parsed completely differently; it's
946+
parsed as a single constraint `1.2.0` with _prerelease_ `1.4.5`.
947+
948+
#### Wildcards In Comparisons
949+
950+
The `x`, `X`, and `*` characters can be used as a wildcard character. This works
951+
for all comparison operators. When used on the `=` operator it falls
952+
back to the patch level comparison (see tilde below). For example,
953+
954+
* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
955+
* `>= 1.2.x` is equivalent to `>= 1.2.0`
956+
* `<= 2.x` is equivalent to `< 3`
957+
* `*` is equivalent to `>= 0.0.0`
958+
959+
#### Tilde Range Comparisons (Patch)
960+
961+
The tilde (`~`) comparison operator is for patch level ranges when a minor
962+
version is specified and major level changes when the minor number is missing.
963+
For example,
964+
965+
* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0`
966+
* `~1` is equivalent to `>= 1, < 2`
967+
* `~2.3` is equivalent to `>= 2.3, < 2.4`
968+
* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0`
969+
* `~1.x` is equivalent to `>= 1, < 2`
970+
971+
#### Caret Range Comparisons (Major)
972+
973+
The caret (`^`) comparison operator is for major level changes once a stable
974+
(1.0.0) release has occurred. Prior to a 1.0.0 release the minor versions acts
975+
as the API stability level. This is useful when comparisons of API versions as a
976+
major change is API breaking. For example,
977+
978+
* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0`
979+
* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0`
980+
* `^2.3` is equivalent to `>= 2.3, < 3`
981+
* `^2.x` is equivalent to `>= 2.0.0, < 3`
982+
* `^0.2.3` is equivalent to `>=0.2.3 <0.3.0`
983+
* `^0.2` is equivalent to `>=0.2.0 <0.3.0`
984+
* `^0.0.3` is equivalent to `>=0.0.3 <0.0.4`
985+
* `^0.0` is equivalent to `>=0.0.0 <0.1.0`
986+
* `^0` is equivalent to `>=0.0.0 <1.0.0`
987+
988+
989+
Examples:
990+
991+
- `IsMatchVersion(resource.attributes["app.version"], "1.2.x")`
992+
993+
- `IsMatchVersion("1.2.3", "~1.2")`
994+
995+
- `IsMatchVersion(attributes["version"], "1.2.0-1.2.5")`
996+
997+
922998
### IsList
923999

9241000
`IsList(value)`
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs // import "github.com/canva/otel-platform/opentelemetry-collector/pkg/ottl/ottlfuncs"
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/Masterminds/semver/v3"
11+
12+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
13+
)
14+
15+
type IsMatchVersionArguments[K any] struct {
16+
Target ottl.StringGetter[K]
17+
Constraint string
18+
}
19+
20+
func NewIsMatchVersionFactory[K any]() ottl.Factory[K] {
21+
return ottl.NewFactory("IsMatchVersion", &IsMatchVersionArguments[K]{}, createIsMatchVersionFunction[K])
22+
}
23+
24+
func createIsMatchVersionFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
25+
args, ok := oArgs.(*IsMatchVersionArguments[K])
26+
27+
if !ok {
28+
return nil, fmt.Errorf("IsMatchVersionFactory args must be of type *IsMatchVersionArguments[K]")
29+
}
30+
31+
return isMatchVersion(args.Target, args.Constraint)
32+
}
33+
34+
func isMatchVersion[K any](target ottl.StringGetter[K], constraint string) (ottl.ExprFunc[K], error) {
35+
semverconstraint, err := semver.NewConstraint(constraint)
36+
if err != nil {
37+
return nil, fmt.Errorf("the constrain supplied to IsMatchVersion is not a valid: %w", err)
38+
}
39+
return func(ctx context.Context, tCtx K) (any, error) {
40+
val, err := target.Get(ctx, tCtx)
41+
42+
if err != nil {
43+
return false, err
44+
}
45+
46+
version, err := semver.NewVersion(val)
47+
48+
if err != nil {
49+
return false, fmt.Errorf("failed to parse semver version from: %s, err: %w", val, err)
50+
}
51+
52+
return semverconstraint.Check(version), nil
53+
}, nil
54+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package ottlfuncs
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/stretchr/testify/assert"
11+
"go.opentelemetry.io/collector/pdata/pcommon"
12+
13+
"github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl"
14+
)
15+
16+
func Test_isMatchVersion(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
value any
20+
constraint string
21+
expected bool
22+
wantErr bool
23+
}{
24+
{
25+
name: "version match",
26+
value: "1.2.3",
27+
constraint: "1.2.x",
28+
expected: true,
29+
wantErr: false,
30+
},
31+
{
32+
name: "version doesn't match",
33+
value: "1.3.3",
34+
constraint: "1.2.x",
35+
expected: false,
36+
wantErr: false,
37+
},
38+
{
39+
name: "version pcommon.ValueTypeStr",
40+
value: pcommon.NewValueStr("1.2.3"),
41+
constraint: "1.2.x",
42+
expected: true,
43+
wantErr: false,
44+
},
45+
{
46+
name: "version pcommon.ValueTypeInt",
47+
value: pcommon.NewValueInt(123),
48+
constraint: "1.2.x",
49+
expected: false,
50+
wantErr: true,
51+
},
52+
{
53+
name: "not valid version string type",
54+
value: "abc.2.3",
55+
constraint: "1.2.x",
56+
expected: false,
57+
wantErr: true,
58+
},
59+
{
60+
name: "nil value",
61+
value: nil,
62+
constraint: "1.2.x",
63+
expected: false,
64+
wantErr: true,
65+
},
66+
{
67+
name: "not valid version pcommon.ValueTypeStr",
68+
value: pcommon.NewValueStr("abc.2.3"),
69+
constraint: "1.2.x",
70+
expected: false,
71+
wantErr: true,
72+
},
73+
}
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
exprFunc, err := isMatchVersion[any](&ottl.StandardStringGetter[any]{
77+
Getter: func(_ context.Context, _ any) (any, error) {
78+
return tt.value, nil
79+
},
80+
}, tt.constraint)
81+
assert.NoError(t, err)
82+
result, err := exprFunc(context.Background(), nil)
83+
84+
assert.True(t, (err != nil) == tt.wantErr, "Expected errors: %t received error: %t, err: %w", tt.wantErr, err != nil, err)
85+
assert.Equal(t, tt.expected, result)
86+
})
87+
}
88+
}
89+
90+
func Test_isMatchVersion_invalid_constrain(t *testing.T) {
91+
target := &ottl.StandardStringGetter[any]{
92+
Getter: func(_ context.Context, _ any) (any, error) {
93+
return "1.2.3", nil
94+
},
95+
}
96+
_, err := isMatchVersion[any](target, "abc.1.2")
97+
assert.Error(t, err)
98+
}

pkg/ottl/ottlfuncs/functions.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,6 @@ func converters[K any]() []ottl.Factory[K] {
9191
NewAppendFactory[K](),
9292
NewYearFactory[K](),
9393
NewHexFactory[K](),
94+
NewIsMatchVersionFactory[K](),
9495
}
9596
}

0 commit comments

Comments
 (0)