Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Uses the new parsing structure for RBAC parsing #3206

Merged
merged 16 commits into from
Sep 16, 2024
Merged
42 changes: 41 additions & 1 deletion apis/v1beta1/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ import (
"github.com/go-logr/logr"
"gopkg.in/yaml.v3"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"

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

Expand Down Expand Up @@ -139,9 +141,43 @@ type Config struct {
Service Service `json:"service" yaml:"service"`
}

// getRbacRulesForComponentKinds gets the RBAC Rules for the given ComponentKind(s).
func (c *Config) getRbacRulesForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]rbacv1.PolicyRule, error) {
var rules []rbacv1.PolicyRule
enabledComponents := c.GetEnabledComponents()
for _, componentKind := range componentKinds {
var retriever components.ParserRetriever
var cfg AnyConfig
switch componentKind {
case KindReceiver:
retriever = receivers.ReceiverFor
cfg = c.Receivers
case KindExporter:
retriever = exporters.ParserFor
cfg = c.Exporters
case KindProcessor:
retriever = processors.ProcessorFor
if c.Processors == nil {
cfg = AnyConfig{}
} else {
cfg = *c.Processors
}
}
for componentName := range enabledComponents[componentKind] {
// TODO: Clean up the naming here and make it simpler to use a retriever.
parser := retriever(componentName)
if parsedRules, err := parser.GetRBACRules(logger, cfg.Object[componentName]); err != nil {
return nil, err
} else {
rules = append(rules, parsedRules...)
}
}
}
return rules, nil
}

// getPortsForComponentKinds gets the ports for the given ComponentKind(s).
func (c *Config) getPortsForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]corev1.ServicePort, error) {

var ports []corev1.ServicePort
enabledComponents := c.GetEnabledComponents()
for _, componentKind := range componentKinds {
Expand Down Expand Up @@ -187,6 +223,10 @@ func (c *Config) GetAllPorts(logger logr.Logger) ([]corev1.ServicePort, error) {
return c.getPortsForComponentKinds(logger, KindReceiver, KindExporter)
}

func (c *Config) GetAllRbacRules(logger logr.Logger) ([]rbacv1.PolicyRule, error) {
return c.getRbacRulesForComponentKinds(logger, KindReceiver, KindExporter, KindProcessor)
}

// Yaml encodes the current object and returns it as a string.
func (c *Config) Yaml() (string, error) {
var buf bytes.Buffer
Expand Down
129 changes: 129 additions & 0 deletions internal/components/builder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// 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"

corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/intstr"

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

type ParserOption[T any] func(*Option[T])

type Option[T any] struct {
jaronoff97 marked this conversation as resolved.
Show resolved Hide resolved
protocol corev1.Protocol
appProtocol *string
targetPort intstr.IntOrString
nodePort int32
name string
port int32
portParser PortParser[T]
rbacGen RBACRuleGenerator[T]
}

func NewEmptyOption[T any]() *Option[T] {
jaronoff97 marked this conversation as resolved.
Show resolved Hide resolved
return &Option[T]{}
}

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

func (o *Option[T]) Apply(opts ...ParserOption[T]) {
for _, opt := range opts {
opt(o)
}
}

func (o *Option[T]) 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 Builder[T any] []ParserOption[T]

func NewBuilder[T any]() Builder[T] {
return []ParserOption[T]{}
}

func (b Builder[T]) WithProtocol(protocol corev1.Protocol) Builder[T] {
return append(b, func(o *Option[T]) {
o.protocol = protocol
})
}
func (b Builder[T]) WithAppProtocol(appProtocol *string) Builder[T] {
return append(b, func(o *Option[T]) {
o.appProtocol = appProtocol
})
}
func (b Builder[T]) WithTargetPort(targetPort int32) Builder[T] {
return append(b, func(o *Option[T]) {
o.targetPort = intstr.FromInt32(targetPort)
})
}
func (b Builder[T]) WithNodePort(nodePort int32) Builder[T] {
return append(b, func(o *Option[T]) {
o.nodePort = nodePort
})
}
func (b Builder[T]) WithName(name string) Builder[T] {
return append(b, func(o *Option[T]) {
o.name = name
})
}
func (b Builder[T]) WithPort(port int32) Builder[T] {
return append(b, func(o *Option[T]) {
o.port = port
})
}
func (b Builder[T]) WithPortParser(portParser PortParser[T]) Builder[T] {
return append(b, func(o *Option[T]) {
o.portParser = portParser
})
}
func (b Builder[T]) WithRbacGen(rbacGen RBACRuleGenerator[T]) Builder[T] {
return append(b, func(o *Option[T]) {
o.rbacGen = rbacGen
})
}

func (b Builder[T]) Build() (*GenericParser[T], error) {
o := NewEmptyOption[T]()
o.Apply(b...)
if len(o.name) == 0 {
return nil, fmt.Errorf("invalid option struct, no name specified")
}
return &GenericParser[T]{name: o.name, portParser: o.portParser, rbacGen: o.rbacGen, option: o}, nil
}

func (b Builder[T]) MustBuild() *GenericParser[T] {
if p, err := b.Build(); err != nil {
panic(err)
} else {
return p
}
}
156 changes: 156 additions & 0 deletions internal/components/builder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// 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"
rbacv1 "k8s.io/api/rbac/v1"

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

func TestBuilder_Build(t *testing.T) {
type sampleConfig struct {
example string

Check failure on line 31 in internal/components/builder_test.go

View workflow job for this annotation

GitHub Actions / Code standards (linting)

field `example` is unused (unused)
number int

Check failure on line 32 in internal/components/builder_test.go

View workflow job for this annotation

GitHub Actions / Code standards (linting)

field `number` is unused (unused)
m map[string]interface{}

Check failure on line 33 in internal/components/builder_test.go

View workflow job for this annotation

GitHub Actions / Code standards (linting)

field `m` is unused (unused)
}
type want struct {
name string
ports []corev1.ServicePort
rules []rbacv1.PolicyRule
}
type fields[T any] struct {
b components.Builder[T]
}
type params struct {
conf interface{}
}
type testCase[T any] struct {
name string
fields fields[T]
params params
want want
wantErr assert.ErrorAssertionFunc
}
examplePortParser := func(logger logr.Logger, name string, defaultPort *corev1.ServicePort, config sampleConfig) ([]corev1.ServicePort, error) {
if defaultPort != nil {
return []corev1.ServicePort{*defaultPort}, nil
}
return nil, nil
}
tests := []testCase[sampleConfig]{
{
name: "basic valid configuration",
fields: fields[sampleConfig]{
b: components.NewBuilder[sampleConfig]().
WithPortParser(examplePortParser).
WithName("test-service").
WithPort(80).
WithNodePort(80).
WithProtocol(corev1.ProtocolTCP),
},
params: params{
conf: sampleConfig{},
},
want: want{
name: "__test-service",
ports: []corev1.ServicePort{
{
Name: "test-service",
Port: 80,
NodePort: 80,
Protocol: corev1.ProtocolTCP,
},
},
rules: nil,
},
wantErr: assert.NoError,
},
{
name: "missing name",
fields: fields[sampleConfig]{
b: components.NewBuilder[sampleConfig]().
WithPort(8080).
WithProtocol(corev1.ProtocolUDP),
},
params: params{
conf: sampleConfig{},
},
want: want{},
wantErr: assert.Error,
},
{
name: "complete configuration with RBAC rules",
fields: fields[sampleConfig]{
b: components.NewBuilder[sampleConfig]().
WithName("secure-service").
WithPort(443).
WithProtocol(corev1.ProtocolTCP).
WithRbacGen(func(logger logr.Logger, config sampleConfig) ([]rbacv1.PolicyRule, error) {
return []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "list"},
},
}, nil
}),
},
params: params{
conf: sampleConfig{},
},
want: want{
name: "__secure-service",
ports: nil,
rules: []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "list"},
},
},
},
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.fields.b.Build()
if tt.wantErr(t, err, fmt.Sprintf("WantErr()")) && err != nil {

Check failure on line 137 in internal/components/builder_test.go

View workflow job for this annotation

GitHub Actions / Code standards (linting)

S1039: unnecessary use of fmt.Sprintf (gosimple)
return
}
assert.Equalf(t, tt.want.name, got.ParserName(), "ParserName()")
ports, err := got.Ports(logr.Discard(), got.ParserType(), tt.params.conf)
assert.NoError(t, err)
assert.Equalf(t, tt.want.ports, ports, "Ports()")
rules, err := got.GetRBACRules(logr.Discard(), tt.params.conf)
assert.NoError(t, err)
assert.Equalf(t, tt.want.rules, rules, "GetRBACRules()")
})
}
}

func TestMustBuildPanics(t *testing.T) {
b := components.Builder[*components.SingleEndpointConfig]{}
assert.Panics(t, func() {
b.MustBuild()
})
}
Loading
Loading