Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
27 changes: 27 additions & 0 deletions .chloggen/ottl-encode.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: ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Add Encode function to OTTL

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

# (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: []
17 changes: 17 additions & 0 deletions pkg/ottl/ottlfuncs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,7 @@ Available Converters:
- [Day](#day)
- [Double](#double)
- [Duration](#duration)
- [Encode](#encode)
- [ExtractPatterns](#extractpatterns)
- [ExtractGrokPatterns](#extractgrokpatterns)
- [FNV](#fnv)
Expand Down Expand Up @@ -740,6 +741,22 @@ Examples:
- `Duration("333ms")`
- `Duration("1000000h")`

### Encode

`Encode(value, encoding)`

The `Encode` Converter takes a string or byte array and returns the string or byte array encoded with the specified encoding.

`value` is a string or byte array to encode.
`encoding` is a valid encoding name included in the [IANA encoding index](https://www.iana.org/assignments/character-sets/character-sets.xhtml) or one of `base64`, `base64-raw`, `base64-url` or `base64-raw-url`.

Examples:

- `Encode("hello world", "base64")`


- `Encode(resource.attributes["field"], "us-ascii")`

### ExtractPatterns

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

package ottlfuncs // import "github.com/open-telemetry/opentelemetry-collector-contrib/pkg/ottl/ottlfuncs"

import (
"context"
"encoding/base64"
"errors"
"fmt"

"go.opentelemetry.io/collector/pdata/pcommon"

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

type EncodeArguments[K any] struct {
Target ottl.Getter[K]
Encoding string
}

func NewEncodeFactory[K any]() ottl.Factory[K] {
return ottl.NewFactory("Encode", &EncodeArguments[K]{}, createEncodeFunction[K])
}

func createEncodeFunction[K any](_ ottl.FunctionContext, oArgs ottl.Arguments) (ottl.ExprFunc[K], error) {
args, ok := oArgs.(*EncodeArguments[K])
if !ok {
return nil, errors.New("EncodeFactory args must be of type *EncodeArguments[K]")
}

return Encode(args.Target, args.Encoding)
}

func Encode[K any](target ottl.Getter[K], encoding string) (ottl.ExprFunc[K], error) {
return func(ctx context.Context, tCtx K) (any, error) {
val, err := target.Get(ctx, tCtx)
if err != nil {
return nil, err
}
var stringValue string

switch v := val.(type) {
case []byte:
stringValue = string(v)
case *string:
stringValue = *v
case string:
stringValue = v
case pcommon.ByteSlice:
stringValue = string(v.AsRaw())
case *pcommon.ByteSlice:
stringValue = string(v.AsRaw())
case pcommon.Value:
stringValue = v.AsString()
case *pcommon.Value:
stringValue = v.AsString()
default:
return nil, fmt.Errorf("unsupported type provided to Encode function: %T", v)
}

switch encoding {
// base64 is not in IANA index, so we have to deal with this encoding separately
case "base64":
return base64.StdEncoding.EncodeToString([]byte(stringValue)), nil
case "base64-raw":
return base64.RawStdEncoding.EncodeToString([]byte(stringValue)), nil
case "base64-url":
return base64.URLEncoding.EncodeToString([]byte(stringValue)), nil
case "base64-raw-url":
return base64.RawURLEncoding.EncodeToString([]byte(stringValue)), nil
default:
e, err := textutils.LookupEncoding(encoding)
if err != nil {
return nil, err
}

encodedString, err := e.NewEncoder().String(stringValue)
if err != nil {
return nil, fmt.Errorf("could not encode: %w", err)
}

return encodedString, nil
}
}, nil
}
210 changes: 210 additions & 0 deletions pkg/ottl/ottlfuncs/func_encode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0

package ottlfuncs

import (
"context"
"testing"

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

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

func TestEncode(t *testing.T) {
testByteSlice := pcommon.NewByteSlice()
testByteSlice.FromRaw([]byte("test string"))
testByteSliceB64 := pcommon.NewByteSlice()
testByteSliceB64.FromRaw([]byte("hello world"))

testValue := pcommon.NewValueEmpty()
_ = testValue.FromRaw("test string")
testValueB64 := pcommon.NewValueEmpty()
_ = testValueB64.FromRaw("hello world")

type testCase struct {
name string
value any
encoding string
want any
expectedError string
}
tests := []testCase{
{
name: "convert byte array to base64",
value: []byte("test\n"),
encoding: "base64",
want: "dGVzdAo=",
},
{
name: "convert string to base64",
value: "hello world",
encoding: "base64",
want: "aGVsbG8gd29ybGQ=",
},
{
name: "convert ByteSlice to base64",
value: testByteSliceB64,
encoding: "base64",
want: "aGVsbG8gd29ybGQ=",
},
{
name: "convert Value to base64",
value: testValueB64,
encoding: "base64",
want: "aGVsbG8gd29ybGQ=",
},
{
name: "convert ByteSlice pointer to base64",
value: &testByteSliceB64,
encoding: "base64",
want: "aGVsbG8gd29ybGQ=",
},
{
name: "convert Value pointer to base64",
value: &testValueB64,
encoding: "base64",
want: "aGVsbG8gd29ybGQ=",
},
{
name: "encode string to us-ascii",
value: "test string",
encoding: "us-ascii",
want: "test string",
},
{
name: "encode byte array to us-ascii",
value: []byte("test string"),
encoding: "us-ascii",
want: "test string",
},
{
name: "encode byte slice to us-ascii",
value: testByteSlice,
encoding: "us-ascii",
want: "test string",
},
{
name: "encode Value to us-ascii",
value: testValue,
encoding: "us-ascii",
want: "test string",
},
{
name: "encode byte slice pointer to us-ascii",
value: &testByteSlice,
encoding: "us-ascii",
want: "test string",
},
{
name: "encode Value pointer to us-ascii",
value: &testValue,
encoding: "us-ascii",
want: "test string",
},
{
name: "encode string to ISO-8859-1",
value: "test string",
encoding: "ISO-8859-1",
want: "test string",
},
{
name: "encode string to WINDOWS-1251",
value: "test string",
encoding: "WINDOWS-1251",
want: "test string",
},
{
name: "encode string to WINDOWS-1252",
value: "test string",
encoding: "WINDOWS-1252",
want: "test string",
},
{
name: "encode string to UTF-8",
value: "test string",
encoding: "UTF-8",
want: "test string",
},
{
name: "encode string to UTF-16 1",
value: "test string",
encoding: "UTF-16",
want: "t\x00e\x00s\x00t\x00 \x00s\x00t\x00r\x00i\x00n\x00g\x00",
},
{
name: "encode string to UTF-16 2",
value: "test string",
encoding: "UTF16",
want: "t\x00e\x00s\x00t\x00 \x00s\x00t\x00r\x00i\x00n\x00g\x00",
},
{
name: "encode string to GB2312; no encoder available",
value: "test string",
encoding: "GB2312",
want: nil,
expectedError: "no charmap defined for encoding 'GB2312'",
},
{
name: "non-string",
value: 10,
encoding: "base64",
expectedError: "unsupported type provided to Encode function: int",
},
{
name: "nil",
value: nil,
encoding: "base64",
expectedError: "unsupported type provided to Encode function: <nil>",
},
{
name: "base64 with url-safe sensitive characters",
value: "Go?/Z~x",
encoding: "base64",
want: "R28/L1p+eA==",
},
{
name: "base64-raw with url-safe sensitive characters",
value: "Go?/Z~x",
encoding: "base64-raw",
want: "R28/L1p+eA",
},
{
name: "base64-url with url-safe sensitive characters",
value: "Go?/Z~x",
encoding: "base64-url",
want: "R28_L1p-eA==",
},
{
name: "base64-raw-url with url-safe sensitive characters",
value: "Go?/Z~x",
encoding: "base64-raw-url",
want: "R28_L1p-eA",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expressionFunc, err := createEncodeFunction[any](ottl.FunctionContext{}, &EncodeArguments[any]{
Target: &ottl.StandardGetSetter[any]{
Getter: func(context.Context, any) (any, error) {
return tt.value, nil
},
},
Encoding: tt.encoding,
})

require.NoError(t, err)

result, err := expressionFunc(nil, nil)
if tt.expectedError != "" {
require.ErrorContains(t, err, tt.expectedError)
return
}

require.NoError(t, err)
require.Equal(t, tt.want, result)
})
}
}
1 change: 1 addition & 0 deletions pkg/ottl/ottlfuncs/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ func converters[K any]() []ottl.Factory[K] {
NewDayFactory[K](),
NewDoubleFactory[K](),
NewDurationFactory[K](),
NewEncodeFactory[K](),
NewExtractPatternsFactory[K](),
NewExtractGrokPatternsFactory[K](),
NewFnvFactory[K](),
Expand Down