Skip to content

Commit 93c409b

Browse files
Per Goncalves da Silvaperdasilva
authored andcommitted
Refactor
Signed-off-by: Per Goncalves da Silva <[email protected]>
1 parent 3e224b1 commit 93c409b

File tree

2 files changed

+105
-148
lines changed

2 files changed

+105
-148
lines changed

internal/operator-controller/rukpak/convert/installer_rbac.go

Lines changed: 84 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -44,180 +44,116 @@ var (
4444
}
4545
)
4646

47-
func GenerateInstallerRBAC(objs []client.Object, extensionName string, installNamespace string, watchNamespace string) []client.Object {
48-
generatedObjs := getGeneratedObjs(objs)
49-
50-
var (
51-
serviceAccountName = extensionName + "-installer"
52-
clusterRoleName = extensionName + "-installer-clusterrole"
53-
clusterRoleBindingName = extensionName + "-installer-clusterrole-binding"
54-
roleName = extensionName + "-installer-role"
55-
roleBindingName = extensionName + "-installer-role-binding"
56-
)
57-
58-
rbacManifests := []client.Object{
59-
// installer service account
60-
ptr.To(newServiceAccount(
61-
installNamespace,
62-
serviceAccountName,
63-
)),
64-
65-
// cluster scoped resources
66-
ptr.To(newClusterRole(
67-
clusterRoleName,
68-
slices.Concat(
69-
// finalizer rule
70-
[]rbacv1.PolicyRule{newClusterExtensionFinalizerPolicyRule(extensionName)},
71-
// cluster scoped resource creation and management rules
72-
generatePolicyRules(filter.Filter(objs, isClusterScopedResource)),
73-
// controller rbac scope
74-
collectRBACResourcePolicyRules(filter.Filter(generatedObjs, isClusterRole)),
75-
),
76-
)),
77-
ptr.To(newClusterRoleBinding(
78-
clusterRoleBindingName,
79-
clusterRoleName,
80-
installNamespace,
81-
serviceAccountName,
82-
)),
83-
84-
// namespace scoped install namespace resources
85-
ptr.To(newRole(
86-
installNamespace,
87-
roleName,
88-
slices.Concat(
89-
// namespace scoped resource creation and management rules
90-
generatePolicyRules(filter.Filter(objs, filter.And(isNamespacedResource, inNamespace(installNamespace)))),
91-
// controller rbac scope
92-
collectRBACResourcePolicyRules(filter.Filter(generatedObjs, filter.And(isRole, inNamespace(installNamespace)))),
93-
),
94-
)),
95-
ptr.To(newRoleBinding(
96-
installNamespace,
97-
roleBindingName,
98-
roleName,
99-
installNamespace,
100-
serviceAccountName,
101-
)),
102-
103-
// namespace scoped watch namespace resources
104-
ptr.To(newRole(
105-
watchNamespace,
106-
roleName,
107-
slices.Concat(
108-
// namespace scoped resource creation and management rules
109-
generatePolicyRules(filter.Filter(objs, filter.And(isNamespacedResource, inNamespace(watchNamespace)))),
110-
// controller rbac scope
111-
collectRBACResourcePolicyRules(filter.Filter(generatedObjs, filter.And(isRole, inNamespace(watchNamespace)))),
112-
),
113-
)),
114-
ptr.To(newRoleBinding(
115-
watchNamespace,
116-
roleBindingName,
117-
roleName,
118-
installNamespace,
119-
serviceAccountName,
120-
)),
121-
}
122-
123-
// remove any cluster/role(s) without any defined rules and pair cluster/role add manifests
124-
return slices.DeleteFunc(rbacManifests, isNoRules)
47+
// GenerateResourceManagerClusterRole generates a ClusterRole with permissions to manage objs resources. The
48+
// permissions also aggregate any permissions from any ClusterRoles in objs allowing the holder to also assign
49+
// the RBAC therein to another service account. Note: currently assumes objs have been created by convert.Convert.
50+
func GenerateResourceManagerClusterRole(objs []client.Object) *rbacv1.ClusterRole {
51+
return ptr.To(newClusterRole(
52+
"",
53+
slices.Concat(
54+
// cluster scoped resource creation and management rules
55+
generatePolicyRules(filter.Filter(objs, isClusterScopedResource)),
56+
// controller rbac scope
57+
collectRBACResourcePolicyRules(filter.Filter(objs, filter.And(isGeneratedResource, isOfKind("ClusterRole")))),
58+
),
59+
))
12560
}
12661

127-
func isNoRules(object client.Object) bool {
128-
switch obj := object.(type) {
129-
case *rbacv1.ClusterRole:
130-
return len(obj.Rules) == 0
131-
case *rbacv1.Role:
132-
return len(obj.Rules) == 0
62+
// GenerateClusterExtensionFinalizerPolicyRule generates a policy rule that allows the holder to update
63+
// finalizer for a ClusterExtension with clusterExtensionName.
64+
func GenerateClusterExtensionFinalizerPolicyRule(clusterExtensionName string) rbacv1.PolicyRule {
65+
return rbacv1.PolicyRule{
66+
APIGroups: []string{"olm.operatorframework.io"},
67+
Resources: []string{"clusterextensions/finalizers"},
68+
Verbs: []string{"update"},
69+
ResourceNames: []string{clusterExtensionName},
13370
}
134-
return false
13571
}
13672

137-
func getGeneratedObjs(plainObjs []client.Object) []client.Object {
138-
return filter.Filter(plainObjs, func(obj client.Object) bool {
139-
// this is a hack that abuses an internal implementation detail
140-
// we should probably annotate the generated resources coming out of convert.Convert
141-
_, ok := obj.(*unstructured.Unstructured)
142-
return ok
143-
})
73+
// GenerateResourceManagerRoles generates one or more Roles with permissions to manage objs resources in their
74+
// namespaces. The permissions also include any permissions defined in any Roles in objs within the namespace, allowing
75+
// the holder to also assign the RBAC therein to another service account.
76+
// Note: currently assumes objs have been created by convert.Convert.
77+
func GenerateResourceManagerRoles(objs []client.Object) []*rbacv1.Role {
78+
return mapToSlice(filter.GroupBy(objs, namespaceName), generateRole)
14479
}
14580

146-
var isNamespacedResource filter.Predicate[client.Object] = func(o client.Object) bool {
147-
return slices.Contains(namespaceScopedResources, o.GetObjectKind().GroupVersionKind().Kind)
148-
}
149-
150-
var isClusterScopedResource filter.Predicate[client.Object] = func(o client.Object) bool {
151-
return slices.Contains(clusterScopedResources, o.GetObjectKind().GroupVersionKind().Kind)
152-
}
153-
154-
var isClusterRole = isOfKind("ClusterRole")
155-
var isRole = isOfKind("Role")
156-
157-
func isOfKind(kind string) filter.Predicate[client.Object] {
158-
return func(o client.Object) bool {
159-
return o.GetObjectKind().GroupVersionKind().Kind == kind
160-
}
161-
}
162-
163-
func inNamespace(namespace string) filter.Predicate[client.Object] {
164-
return func(o client.Object) bool {
165-
return o.GetNamespace() == namespace
166-
}
81+
func generateRole(namespace string, namespaceObjs []client.Object) *rbacv1.Role {
82+
return ptr.To(newRole(
83+
namespace,
84+
"",
85+
slices.Concat(
86+
// namespace scoped resource creation and management rules
87+
generatePolicyRules(namespaceObjs),
88+
// controller rbac scope
89+
collectRBACResourcePolicyRules(filter.Filter(namespaceObjs, filter.And(isOfKind("Role"), isGeneratedResource))),
90+
),
91+
))
16792
}
16893

16994
func generatePolicyRules(objs []client.Object) []rbacv1.PolicyRule {
170-
resourceNameMap := groupResourceNamesByGroupKind(objs)
171-
policyRules := make([]rbacv1.PolicyRule, 0, 2*len(resourceNameMap))
172-
for groupKind, resourceNames := range resourceNameMap {
173-
policyRules = append(policyRules, []rbacv1.PolicyRule{
174-
newPolicyRule(groupKind, unnamedResourceVerbs),
175-
newPolicyRule(groupKind, namedResourceVerbs, resourceNames...),
176-
}...)
177-
}
178-
return policyRules
95+
return slices.Concat(mapToSlice(filter.GroupBy(objs, groupKind), func(gk schema.GroupKind, resources []client.Object) []rbacv1.PolicyRule {
96+
return []rbacv1.PolicyRule{
97+
newPolicyRule(gk, unnamedResourceVerbs),
98+
newPolicyRule(gk, namedResourceVerbs, filter.Map(resources, toResourceName)...),
99+
}
100+
})...)
179101
}
180102

181103
func collectRBACResourcePolicyRules(objs []client.Object) []rbacv1.PolicyRule {
182-
var policyRules []rbacv1.PolicyRule
183-
for _, obj := range objs {
104+
return slices.Concat(filter.Map(objs, func(obj client.Object) []rbacv1.PolicyRule {
184105
if cr, ok := obj.(*rbacv1.ClusterRole); ok {
185-
policyRules = append(policyRules, cr.Rules...)
106+
return cr.Rules
186107
} else if r, ok := obj.(*rbacv1.Role); ok {
187-
policyRules = append(policyRules, r.Rules...)
108+
return r.Rules
188109
} else {
189110
panic(fmt.Sprintf("unexpected type %T", obj))
190111
}
191-
}
192-
return policyRules
112+
})...)
193113
}
194114

195-
func newClusterExtensionFinalizerPolicyRule(clusterExtensionName string) rbacv1.PolicyRule {
115+
func newPolicyRule(groupKind schema.GroupKind, verbs []string, resourceNames ...string) rbacv1.PolicyRule {
196116
return rbacv1.PolicyRule{
197-
APIGroups: []string{"olm.operatorframework.io"},
198-
Resources: []string{"clusterextensions/finalizers"},
199-
Verbs: []string{"update"},
200-
ResourceNames: []string{clusterExtensionName},
117+
APIGroups: []string{groupKind.Group},
118+
Resources: []string{fmt.Sprintf("%ss", strings.ToLower(groupKind.Kind))},
119+
Verbs: verbs,
120+
ResourceNames: resourceNames,
201121
}
202122
}
203123

204-
func groupResourceNamesByGroupKind(objs []client.Object) map[schema.GroupKind][]string {
205-
resourceNames := map[schema.GroupKind][]string{}
206-
for _, obj := range objs {
207-
key := obj.GetObjectKind().GroupVersionKind().GroupKind()
208-
if _, ok := resourceNames[key]; !ok {
209-
resourceNames[key] = []string{}
210-
}
211-
resourceNames[key] = append(resourceNames[key], obj.GetName())
124+
func mapToSlice[K comparable, V any, R any](m map[K]V, fn func(k K, v V) R) []R {
125+
out := make([]R, 0, len(m))
126+
for k, v := range m {
127+
out = append(out, fn(k, v))
212128
}
213-
return resourceNames
129+
return out
214130
}
215131

216-
func newPolicyRule(groupKind schema.GroupKind, verbs []string, resourceNames ...string) rbacv1.PolicyRule {
217-
return rbacv1.PolicyRule{
218-
APIGroups: []string{groupKind.Group},
219-
Resources: []string{fmt.Sprintf("%ss", strings.ToLower(groupKind.Kind))},
220-
Verbs: verbs,
221-
ResourceNames: resourceNames,
132+
func isClusterScopedResource(o client.Object) bool {
133+
return slices.Contains(clusterScopedResources, o.GetObjectKind().GroupVersionKind().Kind)
134+
}
135+
136+
func isOfKind(kind string) filter.Predicate[client.Object] {
137+
return func(o client.Object) bool {
138+
return o.GetObjectKind().GroupVersionKind().Kind == kind
222139
}
223140
}
141+
142+
func isGeneratedResource(o client.Object) bool {
143+
// TODO: this is a hack that abuses an internal implementation detail
144+
// we should probably annotate the generated resources coming out of convert.Convert
145+
_, ok := o.(*unstructured.Unstructured)
146+
return ok
147+
}
148+
149+
func groupKind(obj client.Object) schema.GroupKind {
150+
return obj.GetObjectKind().GroupVersionKind().GroupKind()
151+
}
152+
153+
func namespaceName(obj client.Object) string {
154+
return obj.GetNamespace()
155+
}
156+
157+
func toResourceName(o client.Object) string {
158+
return o.GetName()
159+
}

internal/shared/util/filter/filter.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import "slices"
55
// Predicate returns true if the object should be kept when filtering
66
type Predicate[T any] func(entity T) bool
77

8+
type Key[T any, K comparable] func(entity T) K
9+
10+
type MapFn[S any, V any] func(S) V
11+
812
func And[T any](predicates ...Predicate[T]) Predicate[T] {
913
return func(obj T) bool {
1014
for _, predicate := range predicates {
@@ -50,3 +54,20 @@ func Filter[T any](s []T, test Predicate[T]) []T {
5054
func InPlace[T any](s []T, test Predicate[T]) []T {
5155
return slices.DeleteFunc(s, Not(test))
5256
}
57+
58+
func GroupBy[T any, K comparable](s []T, key Key[T, K]) map[K][]T {
59+
out := map[K][]T{}
60+
for _, value := range s {
61+
k := key(value)
62+
out[k] = append(out[k], value)
63+
}
64+
return out
65+
}
66+
67+
func Map[S, V any](s []S, mapper MapFn[S, V]) []V {
68+
out := make([]V, len(s))
69+
for i := 0; i < len(s); i++ {
70+
out[i] = mapper(s[i])
71+
}
72+
return out
73+
}

0 commit comments

Comments
 (0)