Skip to content

Commit

Permalink
Refactor to use options correctly, remove duplication (#3215)
Browse files Browse the repository at this point in the history
* Refactor to use options correctly, remove duplication

* comment and export

* remove unnecessary option

* rename methods, add test

* comment
  • Loading branch information
jaronoff97 committed Aug 19, 2024
1 parent c97fcb5 commit f02ae89
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 179 deletions.
56 changes: 47 additions & 9 deletions internal/components/component.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"

"github.com/open-telemetry/opentelemetry-operator/internal/naming"
)

var (
Expand All @@ -32,28 +34,64 @@ var (
PortNotFoundErr = errors.New("port should not be empty")
)

// PortParser is a function that returns a list of servicePorts given a config of type T.
type PortParser[T any] func(logger logr.Logger, name string, o *Option, config T) ([]corev1.ServicePort, error)

type PortRetriever interface {
GetPortNum() (int32, error)
GetPortNumOrDefault(logr.Logger, int32) int32
}

type PortBuilderOption func(*corev1.ServicePort)
type Option struct {
protocol corev1.Protocol
appProtocol *string
targetPort intstr.IntOrString
nodePort int32
name string
port int32
}

func NewOption(name string, port int32) *Option {
return &Option{
name: name,
port: port,
}
}

func (o *Option) Apply(opts ...PortBuilderOption) {
for _, opt := range opts {
opt(o)
}
}

func (o *Option) GetServicePort() *corev1.ServicePort {
return &corev1.ServicePort{
Name: naming.PortName(o.name, o.port),
Port: o.port,
Protocol: o.protocol,
AppProtocol: o.appProtocol,
TargetPort: o.targetPort,
NodePort: o.nodePort,
}
}

type PortBuilderOption func(*Option)

func WithTargetPort(targetPort int32) PortBuilderOption {
return func(servicePort *corev1.ServicePort) {
servicePort.TargetPort = intstr.FromInt32(targetPort)
return func(opt *Option) {
opt.targetPort = intstr.FromInt32(targetPort)
}
}

func WithAppProtocol(proto *string) PortBuilderOption {
return func(servicePort *corev1.ServicePort) {
servicePort.AppProtocol = proto
return func(opt *Option) {
opt.appProtocol = proto
}
}

func WithProtocol(proto corev1.Protocol) PortBuilderOption {
return func(servicePort *corev1.ServicePort) {
servicePort.Protocol = proto
return func(opt *Option) {
opt.protocol = proto
}
}

Expand Down Expand Up @@ -92,9 +130,9 @@ func PortFromEndpoint(endpoint string) (int32, error) {
return int32(port), err
}

type ParserRetriever func(string) ComponentPortParser
type ParserRetriever func(string) Parser

type ComponentPortParser interface {
type Parser interface {
// Ports returns the service ports parsed based on the component's configuration where name is the component's name
// of the form "name" or "type/name"
Ports(logger logr.Logger, name string, config interface{}) ([]corev1.ServicePort, error)
Expand Down
10 changes: 5 additions & 5 deletions internal/components/exporters/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import (
)

// registry holds a record of all known receiver parsers.
var registry = make(map[string]components.ComponentPortParser)
var registry = make(map[string]components.Parser)

// Register adds a new parser builder to the list of known builders.
func Register(name string, p components.ComponentPortParser) {
func Register(name string, p components.Parser) {
registry[name] = p
}

Expand All @@ -33,16 +33,16 @@ func IsRegistered(name string) bool {
}

// ParserFor returns a parser builder for the given exporter name.
func ParserFor(name string) components.ComponentPortParser {
func ParserFor(name string) components.Parser {
if parser, ok := registry[components.ComponentType(name)]; ok {
return parser
}
// We want the default for exporters to fail silently.
return components.NewNopParser(components.ComponentType(name), components.UnsetPort)
return components.NewGenericParser[any](components.ComponentType(name), components.UnsetPort, nil)
}

var (
componentParsers = []components.ComponentPortParser{
componentParsers = []components.Parser{
components.NewSinglePortParser("prometheus", 8888),
}
)
Expand Down
60 changes: 60 additions & 0 deletions internal/components/generic_parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package components

import (
"fmt"

"github.com/go-logr/logr"
"github.com/mitchellh/mapstructure"
corev1 "k8s.io/api/core/v1"
)

var (
_ Parser = &GenericParser[SingleEndpointConfig]{}
)

// GenericParser serves as scaffolding for custom parsing logic by isolating
// functionality to idempotent functions.
type GenericParser[T any] struct {
name string
portParser PortParser[T]
option *Option
}

func (g *GenericParser[T]) Ports(logger logr.Logger, name string, config interface{}) ([]corev1.ServicePort, error) {
if g.portParser == nil {
return nil, nil
}
var parsed T
if err := mapstructure.Decode(config, &parsed); err != nil {
return nil, err
}
return g.portParser(logger, name, g.option, parsed)
}

func (g *GenericParser[T]) ParserType() string {
return ComponentType(g.name)
}

func (g *GenericParser[T]) ParserName() string {
return fmt.Sprintf("__%s", g.name)
}

func NewGenericParser[T any](name string, port int32, parser PortParser[T], opts ...PortBuilderOption) *GenericParser[T] {
o := NewOption(name, port)
o.Apply(opts...)
return &GenericParser[T]{name: name, portParser: parser, option: o}
}
115 changes: 115 additions & 0 deletions internal/components/generic_parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright The OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package components_test

import (
"fmt"
"testing"

"github.com/go-logr/logr"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"

"github.com/open-telemetry/opentelemetry-operator/internal/components"
)

func TestGenericParser_GetPorts(t *testing.T) {
type args struct {
logger logr.Logger
config interface{}
}
type testCase[T any] struct {
name string
g *components.GenericParser[T]
args args
want []corev1.ServicePort
wantErr assert.ErrorAssertionFunc
}

tests := []testCase[*components.SingleEndpointConfig]{
{
name: "valid config with endpoint",
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint),
args: args{
logger: logr.Discard(),
config: map[string]interface{}{
"endpoint": "http://localhost:8080",
},
},
want: []corev1.ServicePort{
{
Name: "test",
Port: 8080,
},
},
wantErr: assert.NoError,
},
{
name: "valid config with listen_address",
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint),
args: args{
logger: logr.Discard(),
config: map[string]interface{}{
"listen_address": "0.0.0.0:9090",
},
},
want: []corev1.ServicePort{
{
Name: "test",
Port: 9090,
},
},
wantErr: assert.NoError,
},
{
name: "valid config with listen_address with option",
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint, components.WithProtocol(corev1.ProtocolUDP)),
args: args{
logger: logr.Discard(),
config: map[string]interface{}{
"listen_address": "0.0.0.0:9090",
},
},
want: []corev1.ServicePort{
{
Name: "test",
Port: 9090,
Protocol: corev1.ProtocolUDP,
},
},
wantErr: assert.NoError,
},
{
name: "invalid config with no endpoint or listen_address",
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint),
args: args{
logger: logr.Discard(),
config: map[string]interface{}{},
},
want: []corev1.ServicePort{},
wantErr: assert.Error,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.g.Ports(tt.args.logger, "test", tt.args.config)
if !tt.wantErr(t, err, fmt.Sprintf("GetRBACRules(%v, %v)", tt.args.logger, tt.args.config)) {
return
}
assert.Equalf(t, tt.want, got, "GetRBACRules(%v, %v)", tt.args.logger, tt.args.config)
})
}
}
13 changes: 4 additions & 9 deletions internal/components/multi_endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import (
"github.com/open-telemetry/opentelemetry-operator/internal/naming"
)

var _ ComponentPortParser = &MultiPortReceiver{}
var _ Parser = &MultiPortReceiver{}

// MultiProtocolEndpointConfig represents the minimal struct for a given YAML configuration input containing a map to
// a struct with either endpoint or listen_address.
Expand Down Expand Up @@ -84,13 +84,8 @@ func NewMultiPortReceiver(name string, opts ...MultiPortOption) *MultiPortReceiv

func WithPortMapping(name string, port int32, opts ...PortBuilderOption) MultiPortOption {
return func(parser *MultiPortReceiver) {
servicePort := &corev1.ServicePort{
Name: naming.PortName(fmt.Sprintf("%s-%s", parser.name, name), port),
Port: port,
}
for _, opt := range opts {
opt(servicePort)
}
parser.portMappings[name] = servicePort
o := NewOption(name, port)
o.Apply(opts...)
parser.portMappings[name] = o.GetServicePort()
}
}
48 changes: 0 additions & 48 deletions internal/components/nop_parser.go

This file was deleted.

Loading

0 comments on commit f02ae89

Please sign in to comment.