Skip to content

Commit f02ae89

Browse files
authored
Refactor to use options correctly, remove duplication (#3215)
* Refactor to use options correctly, remove duplication * comment and export * remove unnecessary option * rename methods, add test * comment
1 parent c97fcb5 commit f02ae89

9 files changed

+261
-179
lines changed

internal/components/component.go

+47-9
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import (
2323
"github.com/go-logr/logr"
2424
corev1 "k8s.io/api/core/v1"
2525
"k8s.io/apimachinery/pkg/util/intstr"
26+
27+
"github.com/open-telemetry/opentelemetry-operator/internal/naming"
2628
)
2729

2830
var (
@@ -32,28 +34,64 @@ var (
3234
PortNotFoundErr = errors.New("port should not be empty")
3335
)
3436

37+
// PortParser is a function that returns a list of servicePorts given a config of type T.
38+
type PortParser[T any] func(logger logr.Logger, name string, o *Option, config T) ([]corev1.ServicePort, error)
39+
3540
type PortRetriever interface {
3641
GetPortNum() (int32, error)
3742
GetPortNumOrDefault(logr.Logger, int32) int32
3843
}
3944

40-
type PortBuilderOption func(*corev1.ServicePort)
45+
type Option struct {
46+
protocol corev1.Protocol
47+
appProtocol *string
48+
targetPort intstr.IntOrString
49+
nodePort int32
50+
name string
51+
port int32
52+
}
53+
54+
func NewOption(name string, port int32) *Option {
55+
return &Option{
56+
name: name,
57+
port: port,
58+
}
59+
}
60+
61+
func (o *Option) Apply(opts ...PortBuilderOption) {
62+
for _, opt := range opts {
63+
opt(o)
64+
}
65+
}
66+
67+
func (o *Option) GetServicePort() *corev1.ServicePort {
68+
return &corev1.ServicePort{
69+
Name: naming.PortName(o.name, o.port),
70+
Port: o.port,
71+
Protocol: o.protocol,
72+
AppProtocol: o.appProtocol,
73+
TargetPort: o.targetPort,
74+
NodePort: o.nodePort,
75+
}
76+
}
77+
78+
type PortBuilderOption func(*Option)
4179

4280
func WithTargetPort(targetPort int32) PortBuilderOption {
43-
return func(servicePort *corev1.ServicePort) {
44-
servicePort.TargetPort = intstr.FromInt32(targetPort)
81+
return func(opt *Option) {
82+
opt.targetPort = intstr.FromInt32(targetPort)
4583
}
4684
}
4785

4886
func WithAppProtocol(proto *string) PortBuilderOption {
49-
return func(servicePort *corev1.ServicePort) {
50-
servicePort.AppProtocol = proto
87+
return func(opt *Option) {
88+
opt.appProtocol = proto
5189
}
5290
}
5391

5492
func WithProtocol(proto corev1.Protocol) PortBuilderOption {
55-
return func(servicePort *corev1.ServicePort) {
56-
servicePort.Protocol = proto
93+
return func(opt *Option) {
94+
opt.protocol = proto
5795
}
5896
}
5997

@@ -92,9 +130,9 @@ func PortFromEndpoint(endpoint string) (int32, error) {
92130
return int32(port), err
93131
}
94132

95-
type ParserRetriever func(string) ComponentPortParser
133+
type ParserRetriever func(string) Parser
96134

97-
type ComponentPortParser interface {
135+
type Parser interface {
98136
// Ports returns the service ports parsed based on the component's configuration where name is the component's name
99137
// of the form "name" or "type/name"
100138
Ports(logger logr.Logger, name string, config interface{}) ([]corev1.ServicePort, error)

internal/components/exporters/helpers.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ import (
1919
)
2020

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

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

@@ -33,16 +33,16 @@ func IsRegistered(name string) bool {
3333
}
3434

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

4444
var (
45-
componentParsers = []components.ComponentPortParser{
45+
componentParsers = []components.Parser{
4646
components.NewSinglePortParser("prometheus", 8888),
4747
}
4848
)

internal/components/generic_parser.go

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package components
16+
17+
import (
18+
"fmt"
19+
20+
"github.com/go-logr/logr"
21+
"github.com/mitchellh/mapstructure"
22+
corev1 "k8s.io/api/core/v1"
23+
)
24+
25+
var (
26+
_ Parser = &GenericParser[SingleEndpointConfig]{}
27+
)
28+
29+
// GenericParser serves as scaffolding for custom parsing logic by isolating
30+
// functionality to idempotent functions.
31+
type GenericParser[T any] struct {
32+
name string
33+
portParser PortParser[T]
34+
option *Option
35+
}
36+
37+
func (g *GenericParser[T]) Ports(logger logr.Logger, name string, config interface{}) ([]corev1.ServicePort, error) {
38+
if g.portParser == nil {
39+
return nil, nil
40+
}
41+
var parsed T
42+
if err := mapstructure.Decode(config, &parsed); err != nil {
43+
return nil, err
44+
}
45+
return g.portParser(logger, name, g.option, parsed)
46+
}
47+
48+
func (g *GenericParser[T]) ParserType() string {
49+
return ComponentType(g.name)
50+
}
51+
52+
func (g *GenericParser[T]) ParserName() string {
53+
return fmt.Sprintf("__%s", g.name)
54+
}
55+
56+
func NewGenericParser[T any](name string, port int32, parser PortParser[T], opts ...PortBuilderOption) *GenericParser[T] {
57+
o := NewOption(name, port)
58+
o.Apply(opts...)
59+
return &GenericParser[T]{name: name, portParser: parser, option: o}
60+
}
+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright The OpenTelemetry Authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package components_test
16+
17+
import (
18+
"fmt"
19+
"testing"
20+
21+
"github.com/go-logr/logr"
22+
"github.com/stretchr/testify/assert"
23+
corev1 "k8s.io/api/core/v1"
24+
25+
"github.com/open-telemetry/opentelemetry-operator/internal/components"
26+
)
27+
28+
func TestGenericParser_GetPorts(t *testing.T) {
29+
type args struct {
30+
logger logr.Logger
31+
config interface{}
32+
}
33+
type testCase[T any] struct {
34+
name string
35+
g *components.GenericParser[T]
36+
args args
37+
want []corev1.ServicePort
38+
wantErr assert.ErrorAssertionFunc
39+
}
40+
41+
tests := []testCase[*components.SingleEndpointConfig]{
42+
{
43+
name: "valid config with endpoint",
44+
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint),
45+
args: args{
46+
logger: logr.Discard(),
47+
config: map[string]interface{}{
48+
"endpoint": "http://localhost:8080",
49+
},
50+
},
51+
want: []corev1.ServicePort{
52+
{
53+
Name: "test",
54+
Port: 8080,
55+
},
56+
},
57+
wantErr: assert.NoError,
58+
},
59+
{
60+
name: "valid config with listen_address",
61+
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint),
62+
args: args{
63+
logger: logr.Discard(),
64+
config: map[string]interface{}{
65+
"listen_address": "0.0.0.0:9090",
66+
},
67+
},
68+
want: []corev1.ServicePort{
69+
{
70+
Name: "test",
71+
Port: 9090,
72+
},
73+
},
74+
wantErr: assert.NoError,
75+
},
76+
{
77+
name: "valid config with listen_address with option",
78+
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint, components.WithProtocol(corev1.ProtocolUDP)),
79+
args: args{
80+
logger: logr.Discard(),
81+
config: map[string]interface{}{
82+
"listen_address": "0.0.0.0:9090",
83+
},
84+
},
85+
want: []corev1.ServicePort{
86+
{
87+
Name: "test",
88+
Port: 9090,
89+
Protocol: corev1.ProtocolUDP,
90+
},
91+
},
92+
wantErr: assert.NoError,
93+
},
94+
{
95+
name: "invalid config with no endpoint or listen_address",
96+
g: components.NewGenericParser[*components.SingleEndpointConfig]("test", 0, components.ParseSingleEndpoint),
97+
args: args{
98+
logger: logr.Discard(),
99+
config: map[string]interface{}{},
100+
},
101+
want: []corev1.ServicePort{},
102+
wantErr: assert.Error,
103+
},
104+
}
105+
106+
for _, tt := range tests {
107+
t.Run(tt.name, func(t *testing.T) {
108+
got, err := tt.g.Ports(tt.args.logger, "test", tt.args.config)
109+
if !tt.wantErr(t, err, fmt.Sprintf("GetRBACRules(%v, %v)", tt.args.logger, tt.args.config)) {
110+
return
111+
}
112+
assert.Equalf(t, tt.want, got, "GetRBACRules(%v, %v)", tt.args.logger, tt.args.config)
113+
})
114+
}
115+
}

internal/components/multi_endpoint.go

+4-9
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
"github.com/open-telemetry/opentelemetry-operator/internal/naming"
2525
)
2626

27-
var _ ComponentPortParser = &MultiPortReceiver{}
27+
var _ Parser = &MultiPortReceiver{}
2828

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

8585
func WithPortMapping(name string, port int32, opts ...PortBuilderOption) MultiPortOption {
8686
return func(parser *MultiPortReceiver) {
87-
servicePort := &corev1.ServicePort{
88-
Name: naming.PortName(fmt.Sprintf("%s-%s", parser.name, name), port),
89-
Port: port,
90-
}
91-
for _, opt := range opts {
92-
opt(servicePort)
93-
}
94-
parser.portMappings[name] = servicePort
87+
o := NewOption(name, port)
88+
o.Apply(opts...)
89+
parser.portMappings[name] = o.GetServicePort()
9590
}
9691
}

internal/components/nop_parser.go

-48
This file was deleted.

0 commit comments

Comments
 (0)