|
1 | 1 | package convert
|
2 | 2 |
|
3 | 3 | import (
|
4 |
| - "fmt" |
5 |
| - "github.com/operator-framework/operator-controller/internal/shared/util/filter" |
6 |
| - rbacv1 "k8s.io/api/rbac/v1" |
7 |
| - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" |
8 |
| - "k8s.io/apimachinery/pkg/runtime/schema" |
9 |
| - "k8s.io/utils/ptr" |
10 |
| - "sigs.k8s.io/controller-runtime/pkg/client" |
11 |
| - "slices" |
12 |
| - "strings" |
| 4 | + "fmt" |
| 5 | + "slices" |
| 6 | + "strings" |
| 7 | + |
| 8 | + rbacv1 "k8s.io/api/rbac/v1" |
| 9 | + "k8s.io/apimachinery/pkg/runtime/schema" |
| 10 | + "k8s.io/utils/ptr" |
| 11 | + "sigs.k8s.io/controller-runtime/pkg/client" |
| 12 | + |
| 13 | + slicesutil "github.com/operator-framework/operator-controller/internal/shared/util/filter" |
13 | 14 | )
|
14 | 15 |
|
15 | 16 | var (
|
16 |
| - unnamedResourceVerbs = []string{"create", "list", "watch"} |
17 |
| - namedResourceVerbs = []string{"get", "update", "patch", "delete"} |
18 |
| - |
19 |
| - // clusterScopedResources is a slice of registry+v1 bundle supported cluster scoped resource kinds |
20 |
| - clusterScopedResources = []string{ |
21 |
| - "ClusterRole", |
22 |
| - "ClusterRoleBinding", |
23 |
| - "PriorityClass", |
24 |
| - "ConsoleYAMLSample", |
25 |
| - "ConsoleQuickStart", |
26 |
| - "ConsoleCLIDownload", |
27 |
| - "ConsoleLink", |
28 |
| - "CustomResourceDefinition", |
29 |
| - } |
30 |
| - |
31 |
| - // clusterScopedResources is a slice of registry+v1 bundle supported namespace scoped resource kinds |
32 |
| - namespaceScopedResources = []string{ |
33 |
| - "Secret", |
34 |
| - "ConfigMap", |
35 |
| - "ServiceAccount", |
36 |
| - "Service", |
37 |
| - "Role", |
38 |
| - "RoleBinding", |
39 |
| - "PrometheusRule", |
40 |
| - "ServiceMonitor", |
41 |
| - "PodDisruptionBudget", |
42 |
| - "VerticalPodAutoscaler", |
43 |
| - "Deployment", |
44 |
| - } |
| 17 | + unnamedResourceVerbs = []string{"create", "list", "watch"} |
| 18 | + namedResourceVerbs = []string{"get", "update", "patch", "delete"} |
| 19 | + |
| 20 | + // clusterScopedResources is a slice of registry+v1 bundle supported cluster scoped resource kinds |
| 21 | + clusterScopedResources = []string{ |
| 22 | + "ClusterRole", |
| 23 | + "ClusterRoleBinding", |
| 24 | + "PriorityClass", |
| 25 | + "ConsoleYAMLSample", |
| 26 | + "ConsoleQuickStart", |
| 27 | + "ConsoleCLIDownload", |
| 28 | + "ConsoleLink", |
| 29 | + "CustomResourceDefinition", |
| 30 | + } |
| 31 | + |
| 32 | + // clusterScopedResources is a slice of registry+v1 bundle supported namespace scoped resource kinds |
| 33 | + namespaceScopedResources = []string{ |
| 34 | + "Secret", |
| 35 | + "ConfigMap", |
| 36 | + "ServiceAccount", |
| 37 | + "Service", |
| 38 | + "Role", |
| 39 | + "RoleBinding", |
| 40 | + "PrometheusRule", |
| 41 | + "ServiceMonitor", |
| 42 | + "PodDisruptionBudget", |
| 43 | + "VerticalPodAutoscaler", |
| 44 | + "Deployment", |
| 45 | + } |
45 | 46 | )
|
46 | 47 |
|
47 | 48 | // GenerateResourceManagerClusterRole generates a ClusterRole with permissions to manage objs resources. The
|
48 | 49 | // 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 | +// the RBAC therein to another service account. Note: assumes objs have been created by convert.Convert. |
| 51 | +// The returned ClusterRole will not have set .metadata.name |
50 | 52 | 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 |
| - )) |
| 53 | + rules := slices.Concat( |
| 54 | + // cluster scoped resource creation and management rules |
| 55 | + generatePolicyRules(slicesutil.Filter(objs, isClusterScopedResource)), |
| 56 | + // controller rbac scope |
| 57 | + collectRBACResourcePolicyRules(slicesutil.Filter(objs, slicesutil.And(isGeneratedResource, isOfKind("ClusterRole")))), |
| 58 | + ) |
| 59 | + if len(rules) == 0 { |
| 60 | + return nil |
| 61 | + } |
| 62 | + return ptr.To(newClusterRole("", rules)) |
60 | 63 | }
|
61 | 64 |
|
62 | 65 | // GenerateClusterExtensionFinalizerPolicyRule generates a policy rule that allows the holder to update
|
63 | 66 | // finalizer for a ClusterExtension with clusterExtensionName.
|
64 | 67 | 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}, |
70 |
| - } |
| 68 | + return rbacv1.PolicyRule{ |
| 69 | + APIGroups: []string{"olm.operatorframework.io"}, |
| 70 | + Resources: []string{"clusterextensions/finalizers"}, |
| 71 | + Verbs: []string{"update"}, |
| 72 | + ResourceNames: []string{clusterExtensionName}, |
| 73 | + } |
71 | 74 | }
|
72 | 75 |
|
73 | 76 | // GenerateResourceManagerRoles generates one or more Roles with permissions to manage objs resources in their
|
74 | 77 | // namespaces. The permissions also include any permissions defined in any Roles in objs within the namespace, allowing
|
75 | 78 | // the holder to also assign the RBAC therein to another service account.
|
76 | 79 | // Note: currently assumes objs have been created by convert.Convert.
|
| 80 | +// The returned Roles will not have set .metadata.name |
77 | 81 | func GenerateResourceManagerRoles(objs []client.Object) []*rbacv1.Role {
|
78 |
| - return mapToSlice(filter.GroupBy(objs, namespaceName), generateRole) |
| 82 | + return mapToSlice(slicesutil.GroupBy(slicesutil.Filter(objs, isNamespaceScopedResource), namespaceName), generateRole) |
79 | 83 | }
|
80 | 84 |
|
81 | 85 | 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 |
| - )) |
| 86 | + return ptr.To(newRole( |
| 87 | + namespace, |
| 88 | + "", |
| 89 | + slices.Concat( |
| 90 | + // namespace scoped resource creation and management rules |
| 91 | + generatePolicyRules(namespaceObjs), |
| 92 | + // controller rbac scope |
| 93 | + collectRBACResourcePolicyRules(slicesutil.Filter(namespaceObjs, slicesutil.And(isOfKind("Role"), isGeneratedResource))), |
| 94 | + ), |
| 95 | + )) |
92 | 96 | }
|
93 | 97 |
|
94 | 98 | func generatePolicyRules(objs []client.Object) []rbacv1.PolicyRule {
|
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 |
| - })...) |
| 99 | + return slices.Concat( |
| 100 | + mapToSlice(slicesutil.GroupBy(objs, groupKind), func(gk schema.GroupKind, resources []client.Object) []rbacv1.PolicyRule { |
| 101 | + return []rbacv1.PolicyRule{ |
| 102 | + newPolicyRule(gk, unnamedResourceVerbs), |
| 103 | + newPolicyRule(gk, namedResourceVerbs, slicesutil.Map(resources, toResourceName)...), |
| 104 | + } |
| 105 | + })..., |
| 106 | + ) |
101 | 107 | }
|
102 | 108 |
|
103 | 109 | func collectRBACResourcePolicyRules(objs []client.Object) []rbacv1.PolicyRule {
|
104 |
| - return slices.Concat(filter.Map(objs, func(obj client.Object) []rbacv1.PolicyRule { |
105 |
| - if cr, ok := obj.(*rbacv1.ClusterRole); ok { |
106 |
| - return cr.Rules |
107 |
| - } else if r, ok := obj.(*rbacv1.Role); ok { |
108 |
| - return r.Rules |
109 |
| - } else { |
110 |
| - panic(fmt.Sprintf("unexpected type %T", obj)) |
111 |
| - } |
112 |
| - })...) |
| 110 | + return slices.Concat(slicesutil.Map(objs, func(obj client.Object) []rbacv1.PolicyRule { |
| 111 | + if cr, ok := obj.(*rbacv1.ClusterRole); ok { |
| 112 | + return cr.Rules |
| 113 | + } else if r, ok := obj.(*rbacv1.Role); ok { |
| 114 | + return r.Rules |
| 115 | + } else { |
| 116 | + panic(fmt.Sprintf("unexpected type %T", obj)) |
| 117 | + } |
| 118 | + })...) |
113 | 119 | }
|
114 | 120 |
|
115 | 121 | func newPolicyRule(groupKind schema.GroupKind, verbs []string, resourceNames ...string) rbacv1.PolicyRule {
|
116 |
| - return rbacv1.PolicyRule{ |
117 |
| - APIGroups: []string{groupKind.Group}, |
118 |
| - Resources: []string{fmt.Sprintf("%ss", strings.ToLower(groupKind.Kind))}, |
119 |
| - Verbs: verbs, |
120 |
| - ResourceNames: resourceNames, |
121 |
| - } |
| 122 | + return rbacv1.PolicyRule{ |
| 123 | + APIGroups: []string{groupKind.Group}, |
| 124 | + Resources: []string{fmt.Sprintf("%ss", strings.ToLower(groupKind.Kind))}, |
| 125 | + Verbs: verbs, |
| 126 | + ResourceNames: resourceNames, |
| 127 | + } |
122 | 128 | }
|
123 | 129 |
|
124 | 130 | 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)) |
128 |
| - } |
129 |
| - return out |
| 131 | + out := make([]R, 0, len(m)) |
| 132 | + for k, v := range m { |
| 133 | + out = append(out, fn(k, v)) |
| 134 | + } |
| 135 | + return out |
130 | 136 | }
|
131 | 137 |
|
132 | 138 | func isClusterScopedResource(o client.Object) bool {
|
133 |
| - return slices.Contains(clusterScopedResources, o.GetObjectKind().GroupVersionKind().Kind) |
| 139 | + return slices.Contains(clusterScopedResources, o.GetObjectKind().GroupVersionKind().Kind) |
| 140 | +} |
| 141 | + |
| 142 | +func isNamespaceScopedResource(o client.Object) bool { |
| 143 | + return slices.Contains(namespaceScopedResources, o.GetObjectKind().GroupVersionKind().Kind) |
134 | 144 | }
|
135 | 145 |
|
136 |
| -func isOfKind(kind string) filter.Predicate[client.Object] { |
137 |
| - return func(o client.Object) bool { |
138 |
| - return o.GetObjectKind().GroupVersionKind().Kind == kind |
139 |
| - } |
| 146 | +func isOfKind(kind string) slicesutil.Predicate[client.Object] { |
| 147 | + return func(o client.Object) bool { |
| 148 | + return o.GetObjectKind().GroupVersionKind().Kind == kind |
| 149 | + } |
140 | 150 | }
|
141 | 151 |
|
142 | 152 | 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 |
| 153 | + annotations := o.GetAnnotations() |
| 154 | + _, ok := annotations[AnnotationRegistryV1GeneratedManifest] |
| 155 | + return ok |
147 | 156 | }
|
148 | 157 |
|
149 | 158 | func groupKind(obj client.Object) schema.GroupKind {
|
150 |
| - return obj.GetObjectKind().GroupVersionKind().GroupKind() |
| 159 | + return obj.GetObjectKind().GroupVersionKind().GroupKind() |
151 | 160 | }
|
152 | 161 |
|
153 | 162 | func namespaceName(obj client.Object) string {
|
154 |
| - return obj.GetNamespace() |
| 163 | + return obj.GetNamespace() |
155 | 164 | }
|
156 | 165 |
|
157 | 166 | func toResourceName(o client.Object) string {
|
158 |
| - return o.GetName() |
| 167 | + return o.GetName() |
159 | 168 | }
|
0 commit comments