Skip to content

Commit d1e2068

Browse files
author
Israel Blancas
committed
Add automatic RBAC creation for prometheus receiver
Signed-off-by: Israel Blancas <[email protected]>
1 parent 1108621 commit d1e2068

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1321
-81
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
2+
change_type: enhancement
3+
4+
# The name of the component, or a single word describing the area of concern, (e.g. collector, target allocator, auto-instrumentation, opamp, github action)
5+
component: collector
6+
7+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
8+
note: Create RBAC automatically for the Prometheus receiver
9+
10+
# One or more tracking issues related to the change
11+
issues: [3078]
12+
13+
# (Optional) One or more lines of additional information to render under the primary note.
14+
# These lines will be padded with 2 spaces and then inserted directly into the document.
15+
# Use pipe (|) for multiline entries.
16+
subtext:

Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ add-rbac-permissions-to-operator: manifests kustomize
209209
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/cronjobs.yaml
210210
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/daemonsets.yaml
211211
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/events.yaml
212+
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/endpoints.yaml
212213
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/extensions.yaml
213214
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/namespaces.yaml
214215
cd config/rbac && $(KUSTOMIZE) edit add patch --kind ClusterRole --name manager-role --path extra-permissions-operator/namespaces-status.yaml

apis/v1beta1/config.go

+74-4
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ type Config struct {
153153
}
154154

155155
// getRbacRulesForComponentKinds gets the RBAC Rules for the given ComponentKind(s).
156-
func (c *Config) getRbacRulesForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]rbacv1.PolicyRule, error) {
156+
func (c *Config) getClusterRoleRbacRulesForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]rbacv1.PolicyRule, error) {
157157
var rules []rbacv1.PolicyRule
158158
enabledComponents := c.GetEnabledComponents()
159159
for _, componentKind := range componentKinds {
@@ -179,7 +179,7 @@ func (c *Config) getRbacRulesForComponentKinds(logger logr.Logger, componentKind
179179
for componentName := range enabledComponents[componentKind] {
180180
// TODO: Clean up the naming here and make it simpler to use a retriever.
181181
parser := retriever(componentName)
182-
if parsedRules, err := parser.GetRBACRules(logger, cfg.Object[componentName]); err != nil {
182+
if parsedRules, err := parser.GetClusterRoleRules(logger, cfg.Object[componentName]); err != nil {
183183
return nil, err
184184
} else {
185185
rules = append(rules, parsedRules...)
@@ -189,6 +189,68 @@ func (c *Config) getRbacRulesForComponentKinds(logger logr.Logger, componentKind
189189
return rules, nil
190190
}
191191

192+
// getRbacRolesForComponentKinds gets the RBAC Roles for the given ComponentKind(s).
193+
func (c *Config) getRbacRolesForComponentKinds(logger logr.Logger, otelCollectorName string, componentKinds ...ComponentKind) ([]*rbacv1.Role, error) {
194+
var roles []*rbacv1.Role
195+
enabledComponents := c.GetEnabledComponents()
196+
for _, componentKind := range componentKinds {
197+
var retriever components.ParserRetriever
198+
var cfg AnyConfig
199+
switch componentKind {
200+
case KindReceiver:
201+
retriever = receivers.ReceiverFor
202+
cfg = c.Receivers
203+
case KindExporter:
204+
continue
205+
case KindProcessor:
206+
continue
207+
case KindExtension:
208+
continue
209+
}
210+
for componentName := range enabledComponents[componentKind] {
211+
// TODO: Clean up the naming here and make it simpler to use a retriever.
212+
parser := retriever(componentName)
213+
if parsedRoles, err := parser.GetRbacRoles(logger, otelCollectorName, cfg.Object[componentName]); err != nil {
214+
return nil, err
215+
} else {
216+
roles = append(roles, parsedRoles...)
217+
}
218+
}
219+
}
220+
return roles, nil
221+
}
222+
223+
// getRbacRoleBindingsForComponentKinds gets the RBAC RoleBindings for the given ComponentKind(s).
224+
func (c *Config) getRbacRoleBindingsForComponentKinds(logger logr.Logger, serviceAccountName string, otelCollectorName string, otelCollectorNamespace string, componentKinds ...ComponentKind) ([]*rbacv1.RoleBinding, error) {
225+
var roleBindings []*rbacv1.RoleBinding
226+
enabledComponents := c.GetEnabledComponents()
227+
for _, componentKind := range componentKinds {
228+
var retriever components.ParserRetriever
229+
var cfg AnyConfig
230+
switch componentKind {
231+
case KindReceiver:
232+
retriever = receivers.ReceiverFor
233+
cfg = c.Receivers
234+
case KindExporter:
235+
continue
236+
case KindProcessor:
237+
continue
238+
case KindExtension:
239+
continue
240+
}
241+
for componentName := range enabledComponents[componentKind] {
242+
// TODO: Clean up the naming here and make it simpler to use a retriever.
243+
parser := retriever(componentName)
244+
if parsedRoleBindings, err := parser.GetRbacRoleBindings(logger, otelCollectorName, cfg.Object[componentName], serviceAccountName, otelCollectorNamespace); err != nil {
245+
return nil, err
246+
} else {
247+
roleBindings = append(roleBindings, parsedRoleBindings...)
248+
}
249+
}
250+
}
251+
return roleBindings, nil
252+
}
253+
192254
// getPortsForComponentKinds gets the ports for the given ComponentKind(s).
193255
func (c *Config) getPortsForComponentKinds(logger logr.Logger, componentKinds ...ComponentKind) ([]corev1.ServicePort, error) {
194256
var ports []corev1.ServicePort
@@ -326,8 +388,16 @@ func (c *Config) GetEnvironmentVariables(logger logr.Logger) ([]corev1.EnvVar, e
326388
return c.getEnvironmentVariablesForComponentKinds(logger, KindReceiver)
327389
}
328390

329-
func (c *Config) GetAllRbacRules(logger logr.Logger) ([]rbacv1.PolicyRule, error) {
330-
return c.getRbacRulesForComponentKinds(logger, KindReceiver, KindExporter, KindProcessor)
391+
func (c *Config) GetAllClusterRoleRbacRules(logger logr.Logger) ([]rbacv1.PolicyRule, error) {
392+
return c.getClusterRoleRbacRulesForComponentKinds(logger, KindReceiver, KindExporter, KindProcessor)
393+
}
394+
395+
func (c *Config) GetAllRbacRoles(logger logr.Logger, otelCollectorName string) ([]*rbacv1.Role, error) {
396+
return c.getRbacRolesForComponentKinds(logger, otelCollectorName, KindReceiver, KindExporter, KindProcessor)
397+
}
398+
399+
func (c *Config) GetAllRbacRoleBindings(logger logr.Logger, serviceAccountName string, otelCollectorName string, otelCollectorNamespace string) ([]*rbacv1.RoleBinding, error) {
400+
return c.getRbacRoleBindingsForComponentKinds(logger, serviceAccountName, otelCollectorName, otelCollectorNamespace, KindReceiver, KindExporter, KindProcessor)
331401
}
332402

333403
func (c *Config) ApplyDefaults(logger logr.Logger) error {

controllers/common.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -144,16 +144,22 @@ func reconcileDesiredObjects(ctx context.Context, kubeClient client.Client, logg
144144
"object_kind", desired.GetObjectKind(),
145145
)
146146
if isNamespaceScoped(desired) {
147-
if setErr := ctrl.SetControllerReference(owner, desired, scheme); setErr != nil {
148-
l.Error(setErr, "failed to set controller owner reference to desired")
149-
errs = append(errs, setErr)
150-
continue
147+
switch desired.(type) {
148+
case *rbacv1.Role, *rbacv1.RoleBinding:
149+
l.Info("skipping setting controller reference for role or rolebinding")
150+
default:
151+
if setErr := ctrl.SetControllerReference(owner, desired, scheme); setErr != nil {
152+
l.Error(setErr, "failed to set controller owner reference to desired")
153+
errs = append(errs, setErr)
154+
continue
155+
}
151156
}
152157
}
153158
// existing is an object the controller runtime will hydrate for us
154159
// we obtain the existing object by deep copying the desired object because it's the most convenient way
155160
existing := desired.DeepCopyObject().(client.Object)
156161
mutateFn := manifests.MutateFuncFor(existing, desired)
162+
157163
var op controllerutil.OperationResult
158164
crudErr := retry.RetryOnConflict(retry.DefaultRetry, func() error {
159165
result, createOrUpdateErr := ctrl.CreateOrUpdate(ctx, kubeClient, existing, mutateFn)

controllers/opentelemetrycollector_controller.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ var (
5656
ownedClusterObjectTypes = []client.Object{
5757
&rbacv1.ClusterRole{},
5858
&rbacv1.ClusterRoleBinding{},
59+
&rbacv1.Role{},
60+
&rbacv1.RoleBinding{},
5961
}
6062
)
6163

@@ -107,7 +109,7 @@ func (r *OpenTelemetryCollectorReconciler) findOtelOwnedObjects(ctx context.Cont
107109
}
108110
}
109111
if params.Config.CreateRBACPermissions() == rbac.Available {
110-
objs, err := r.findClusterRoleObjects(ctx, params)
112+
objs, err := r.findRBACObjects(ctx, params)
111113
if err != nil {
112114
return nil, err
113115
}
@@ -129,11 +131,15 @@ func (r *OpenTelemetryCollectorReconciler) findOtelOwnedObjects(ctx context.Cont
129131
return ownedObjects, nil
130132
}
131133

132-
// The cluster scope objects do not have owner reference.
133-
func (r *OpenTelemetryCollectorReconciler) findClusterRoleObjects(ctx context.Context, params manifests.Params) (map[types.UID]client.Object, error) {
134+
// findRBACObjects finds ClusterRoles, ClusterRoleBindings, Roles, and RoleBindings.
135+
// Those objects do not have owner references.
136+
// - ClusterRoles and ClusterRoleBindings cannot have owner references
137+
// - Roles and RoleBindings can exist in a different namespace than the OpenTelemetryCollector
138+
//
139+
// Users might switch off the RBAC creation feature on the operator which should remove existing RBAC.
140+
func (r *OpenTelemetryCollectorReconciler) findRBACObjects(ctx context.Context, params manifests.Params) (map[types.UID]client.Object, error) {
134141
ownedObjects := map[types.UID]client.Object{}
135-
// Remove cluster roles and bindings.
136-
// Users might switch off the RBAC creation feature on the operator which should remove existing RBAC.
142+
137143
listOpsCluster := &client.ListOptions{
138144
LabelSelector: labels.SelectorFromSet(manifestutils.SelectorLabels(params.OtelCol.ObjectMeta, collector.ComponentOpenTelemetryCollector)),
139145
}
@@ -320,6 +326,8 @@ func (r *OpenTelemetryCollectorReconciler) SetupWithManager(mgr ctrl.Manager) er
320326
if r.config.CreateRBACPermissions() == rbac.Available {
321327
builder.Owns(&rbacv1.ClusterRoleBinding{})
322328
builder.Owns(&rbacv1.ClusterRole{})
329+
builder.Owns(&rbacv1.RoleBinding{})
330+
builder.Owns(&rbacv1.Role{})
323331
}
324332

325333
if featuregate.PrometheusOperatorIsAvailable.IsEnabled() && r.config.PrometheusCRAvailability() == prometheus.Available {
@@ -338,7 +346,7 @@ const collectorFinalizer = "opentelemetrycollector.opentelemetry.io/finalizer"
338346
func (r *OpenTelemetryCollectorReconciler) finalizeCollector(ctx context.Context, params manifests.Params) error {
339347
// The cluster scope objects do not have owner reference. They need to be deleted explicitly
340348
if params.Config.CreateRBACPermissions() == rbac.Available {
341-
objects, err := r.findClusterRoleObjects(ctx, params)
349+
objects, err := r.findRBACObjects(ctx, params)
342350
if err != nil {
343351
return err
344352
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
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 controllers
16+
17+
import (
18+
"context"
19+
"testing"
20+
21+
"github.com/stretchr/testify/assert"
22+
"github.com/stretchr/testify/require"
23+
corev1 "k8s.io/api/core/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/client-go/tools/record"
27+
ctrl "sigs.k8s.io/controller-runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/client/fake"
29+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
30+
31+
"github.com/open-telemetry/opentelemetry-operator/apis/v1beta1"
32+
"github.com/open-telemetry/opentelemetry-operator/internal/config"
33+
)
34+
35+
func TestReconcile(t *testing.T) {
36+
logger := zap.New()
37+
ctx := context.Background()
38+
39+
scheme := runtime.NewScheme()
40+
require.NoError(t, v1beta1.AddToScheme(scheme))
41+
require.NoError(t, corev1.AddToScheme(scheme))
42+
43+
tests := []struct {
44+
name string
45+
existingState []runtime.Object
46+
expectedResult ctrl.Result
47+
expectedError bool
48+
}{
49+
{
50+
name: "collector not found",
51+
existingState: []runtime.Object{},
52+
expectedResult: ctrl.Result{},
53+
expectedError: false,
54+
},
55+
{
56+
name: "unmanaged collector",
57+
existingState: []runtime.Object{
58+
&v1beta1.OpenTelemetryCollector{
59+
ObjectMeta: metav1.ObjectMeta{
60+
Name: "test-collector",
61+
Namespace: "default",
62+
},
63+
Spec: v1beta1.OpenTelemetryCollectorSpec{
64+
OpenTelemetryCommonFields: v1beta1.OpenTelemetryCommonFields{
65+
ManagementState: v1beta1.ManagementStateUnmanaged,
66+
},
67+
},
68+
},
69+
},
70+
expectedResult: ctrl.Result{},
71+
expectedError: false,
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
client := fake.NewClientBuilder().
78+
WithScheme(scheme).
79+
WithRuntimeObjects(tt.existingState...).
80+
Build()
81+
82+
r := &OpenTelemetryCollectorReconciler{
83+
Client: client,
84+
log: logger,
85+
scheme: scheme,
86+
config: config.New(),
87+
recorder: record.NewFakeRecorder(100),
88+
}
89+
90+
result, err := r.Reconcile(ctx, ctrl.Request{})
91+
92+
if tt.expectedError {
93+
assert.Error(t, err)
94+
} else {
95+
assert.NoError(t, err)
96+
}
97+
assert.Equal(t, tt.expectedResult, result)
98+
})
99+
}
100+
}
101+
102+
func TestGetConfigMapsToRemove(t *testing.T) {
103+
r := &OpenTelemetryCollectorReconciler{}
104+
105+
tests := []struct {
106+
name string
107+
configVersions int
108+
existingConfigMaps *corev1.ConfigMapList
109+
expectedLength int
110+
}{
111+
{
112+
name: "no config maps",
113+
configVersions: 1,
114+
existingConfigMaps: &corev1.ConfigMapList{
115+
Items: []corev1.ConfigMap{},
116+
},
117+
expectedLength: 0,
118+
},
119+
{
120+
name: "keep one config map",
121+
configVersions: 1,
122+
existingConfigMaps: &corev1.ConfigMapList{
123+
Items: []corev1.ConfigMap{
124+
{
125+
ObjectMeta: metav1.ObjectMeta{
126+
CreationTimestamp: metav1.Now(),
127+
},
128+
},
129+
{
130+
ObjectMeta: metav1.ObjectMeta{
131+
CreationTimestamp: metav1.Now(),
132+
},
133+
},
134+
},
135+
},
136+
expectedLength: 0,
137+
},
138+
}
139+
140+
for _, tt := range tests {
141+
t.Run(tt.name, func(t *testing.T) {
142+
result := r.getConfigMapsToRemove(tt.configVersions, tt.existingConfigMaps)
143+
assert.Equal(t, tt.expectedLength, len(result))
144+
})
145+
}
146+
}
147+
148+
func TestNewReconciler(t *testing.T) {
149+
scheme := runtime.NewScheme()
150+
client := fake.NewClientBuilder().WithScheme(scheme).Build()
151+
recorder := record.NewFakeRecorder(100)
152+
logger := zap.New()
153+
cfg := config.New()
154+
155+
params := Params{
156+
Client: client,
157+
Recorder: recorder,
158+
Scheme: scheme,
159+
Log: logger,
160+
Config: cfg,
161+
}
162+
163+
r := NewReconciler(params)
164+
165+
assert.Equal(t, client, r.Client)
166+
assert.Equal(t, recorder, r.recorder)
167+
assert.Equal(t, scheme, r.scheme)
168+
assert.Equal(t, logger, r.log)
169+
assert.Equal(t, cfg, r.config)
170+
}

0 commit comments

Comments
 (0)