@@ -7,37 +7,25 @@ import (
77 "fmt"
88 "io/fs"
99 "path/filepath"
10- "strings"
1110
1211 "helm.sh/helm/v3/pkg/chart"
13- appsv1 "k8s.io/api/apps/v1"
14- corev1 "k8s.io/api/core/v1"
15- rbacv1 "k8s.io/api/rbac/v1"
1612 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
17- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1813 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1914 "k8s.io/apimachinery/pkg/runtime"
2015 "k8s.io/apimachinery/pkg/util/sets"
2116 "k8s.io/cli-runtime/pkg/resource"
22- "k8s.io/utils/ptr"
2317 "sigs.k8s.io/controller-runtime/pkg/client"
2418 "sigs.k8s.io/yaml"
2519
2620 "github.com/operator-framework/api/pkg/operators/v1alpha1"
2721 "github.com/operator-framework/operator-registry/alpha/property"
28- registrybundle "github.com/operator-framework/operator-registry/pkg/lib/bundle"
2922
3023 registry "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/operator-registry"
31- "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
24+ "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render"
25+ "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/generators"
26+ "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/render/validators"
3227)
3328
34- type RegistryV1 struct {
35- PackageName string
36- CSV v1alpha1.ClusterServiceVersion
37- CRDs []apiextensionsv1.CustomResourceDefinition
38- Others []unstructured.Unstructured
39- }
40-
4129type Plain struct {
4230 Objects []client.Object
4331}
@@ -70,16 +58,16 @@ func RegistryV1ToHelmChart(rv1 fs.FS, installNamespace string, watchNamespace st
7058 return chrt, nil
7159}
7260
73- // ParseFS converts the rv1 filesystem into a RegistryV1.
61+ // ParseFS converts the rv1 filesystem into a render. RegistryV1.
7462// ParseFS expects the filesystem to conform to the registry+v1 format:
7563// metadata/annotations.yaml
7664// manifests/
7765// - csv.yaml
7866// - ...
7967//
8068// manifests directory does not contain subdirectories
81- func ParseFS(rv1 fs.FS) (RegistryV1, error) {
82- reg := RegistryV1{}
69+ func ParseFS(rv1 fs.FS) (render. RegistryV1, error) {
70+ reg := render. RegistryV1{}
8371 annotationsFileData, err := fs.ReadFile(rv1, filepath.Join("metadata", "annotations.yaml"))
8472 if err != nil {
8573 return reg, err
@@ -224,22 +212,23 @@ func validateTargetNamespaces(supportedInstallModes sets.Set[string], installNam
224212 return fmt.Errorf("supported install modes %v do not support target namespaces %v", sets.List[string](supportedInstallModes), targetNamespaces)
225213}
226214
227- func saNameOrDefault(saName string) string {
228- if saName == "" {
229- return "default"
230- }
231- return saName
215+ var PlainConverter = Converter{
216+ BundleRenderer: render.BundleRenderer{
217+ BundleValidator: validators.RegistryV1BundleValidator,
218+ ResourceGenerators: []render.ResourceGenerator{
219+ generators.BundleCSVRBACResourceGenerator.ResourceGenerator(),
220+ generators.BundleCRDGenerator,
221+ generators.BundleAdditionalResourcesGenerator,
222+ generators.BundleCSVDeploymentGenerator,
223+ },
224+ },
232225}
233226
234227type Converter struct {
235- BundleValidator BundleValidator
228+ render.BundleRenderer
236229}
237230
238- func (c Converter) Convert(rv1 RegistryV1, installNamespace string, targetNamespaces []string) (*Plain, error) {
239- if err := c.BundleValidator.Validate(&rv1); err != nil {
240- return nil, err
241- }
242-
231+ func (c Converter) Convert(rv1 render.RegistryV1, installNamespace string, targetNamespaces []string) (*Plain, error) {
243232 if installNamespace == "" {
244233 installNamespace = rv1.CSV.Annotations["operatorframework.io/suggested-namespace"]
245234 }
@@ -272,246 +261,9 @@ func (c Converter) Convert(rv1 RegistryV1, installNamespace string, targetNamesp
272261 return nil, fmt.Errorf("webhookDefinitions are not supported")
273262 }
274263
275- deployments := []appsv1.Deployment{}
276- serviceAccounts := map[string]corev1.ServiceAccount{}
277- for _, depSpec := range rv1.CSV.Spec.InstallStrategy.StrategySpec.DeploymentSpecs {
278- annotations := util.MergeMaps(rv1.CSV.Annotations, depSpec.Spec.Template.Annotations)
279- annotations["olm.targetNamespaces"] = strings.Join(targetNamespaces, ",")
280- depSpec.Spec.Template.Annotations = annotations
281-
282- // Hardcode the deployment with RevisionHistoryLimit=1 to replicate OLMv0 behavior
283- // https://github.com/operator-framework/operator-lifecycle-manager/blob/dfd0b2bea85038d3c0d65348bc812d297f16b8d2/pkg/controller/install/deployment.go#L181
284- depSpec.Spec.RevisionHistoryLimit = ptr.To(int32(1))
285-
286- deployments = append(deployments, appsv1.Deployment{
287- TypeMeta: metav1.TypeMeta{
288- Kind: "Deployment",
289- APIVersion: appsv1.SchemeGroupVersion.String(),
290- },
291-
292- ObjectMeta: metav1.ObjectMeta{
293- Namespace: installNamespace,
294- Name: depSpec.Name,
295- Labels: depSpec.Label,
296- },
297- Spec: depSpec.Spec,
298- })
299- saName := saNameOrDefault(depSpec.Spec.Template.Spec.ServiceAccountName)
300- serviceAccounts[saName] = newServiceAccount(installNamespace, saName)
301- }
302-
303- // NOTES:
304- // 1. There's an extra Role for OperatorConditions: get/update/patch; resourceName=csv.name
305- // - This is managed by the OperatorConditions controller here: https://github.com/operator-framework/operator-lifecycle-manager/blob/9ced412f3e263b8827680dc0ad3477327cd9a508/pkg/controller/operators/operatorcondition_controller.go#L106-L109
306- // 2. There's an extra RoleBinding for the above mentioned role.
307- // - Every SA mentioned in the OperatorCondition.spec.serviceAccounts is a subject for this role binding: https://github.com/operator-framework/operator-lifecycle-manager/blob/9ced412f3e263b8827680dc0ad3477327cd9a508/pkg/controller/operators/operatorcondition_controller.go#L171-L177
308- // 3. strategySpec.permissions are _also_ given a clusterrole/clusterrole binding.
309- // - (for AllNamespaces mode only?)
310- // - (where does the extra namespaces get/list/watch rule come from?)
311-
312- roles := []rbacv1.Role{}
313- roleBindings := []rbacv1.RoleBinding{}
314- clusterRoles := []rbacv1.ClusterRole{}
315- clusterRoleBindings := []rbacv1.ClusterRoleBinding{}
316-
317- permissions := rv1.CSV.Spec.InstallStrategy.StrategySpec.Permissions
318- clusterPermissions := rv1.CSV.Spec.InstallStrategy.StrategySpec.ClusterPermissions
319- allPermissions := append(permissions, clusterPermissions...)
320-
321- // Create all the service accounts
322- for _, permission := range allPermissions {
323- saName := saNameOrDefault(permission.ServiceAccountName)
324- if _, ok := serviceAccounts[saName]; !ok {
325- serviceAccounts[saName] = newServiceAccount(installNamespace, saName)
326- }
327- }
328-
329- // If we're in AllNamespaces mode, promote the permissions to clusterPermissions
330- if len(targetNamespaces) == 1 && targetNamespaces[0] == "" {
331- for _, p := range permissions {
332- p.Rules = append(p.Rules, rbacv1.PolicyRule{
333- Verbs: []string{"get", "list", "watch"},
334- APIGroups: []string{corev1.GroupName},
335- Resources: []string{"namespaces"},
336- })
337- clusterPermissions = append(clusterPermissions, p)
338- }
339- permissions = nil
340- }
341-
342- for _, ns := range targetNamespaces {
343- for _, permission := range permissions {
344- saName := saNameOrDefault(permission.ServiceAccountName)
345- name, err := generateName(fmt.Sprintf("%s-%s", rv1.CSV.Name, saName), permission)
346- if err != nil {
347- return nil, err
348- }
349- roles = append(roles, newRole(ns, name, permission.Rules))
350- roleBindings = append(roleBindings, newRoleBinding(ns, name, name, installNamespace, saName))
351- }
352- }
353-
354- for _, permission := range clusterPermissions {
355- saName := saNameOrDefault(permission.ServiceAccountName)
356- name, err := generateName(fmt.Sprintf("%s-%s", rv1.CSV.Name, saName), permission)
357- if err != nil {
358- return nil, err
359- }
360- clusterRoles = append(clusterRoles, newClusterRole(name, permission.Rules))
361- clusterRoleBindings = append(clusterRoleBindings, newClusterRoleBinding(name, name, installNamespace, saName))
362- }
363-
364- objs := []client.Object{}
365- for _, obj := range serviceAccounts {
366- obj := obj
367- if obj.GetName() != "default" {
368- objs = append(objs, &obj)
369- }
370- }
371- for _, obj := range roles {
372- obj := obj
373- objs = append(objs, &obj)
374- }
375- for _, obj := range roleBindings {
376- obj := obj
377- objs = append(objs, &obj)
378- }
379- for _, obj := range clusterRoles {
380- obj := obj
381- objs = append(objs, &obj)
382- }
383- for _, obj := range clusterRoleBindings {
384- obj := obj
385- objs = append(objs, &obj)
386- }
387- for _, obj := range rv1.CRDs {
388- objs = append(objs, &obj)
389- }
390- for _, obj := range rv1.Others {
391- obj := obj
392- supported, namespaced := registrybundle.IsSupported(obj.GetKind())
393- if !supported {
394- return nil, fmt.Errorf("bundle contains unsupported resource: Name: %v, Kind: %v", obj.GetName(), obj.GetKind())
395- }
396- if namespaced {
397- obj.SetNamespace(installNamespace)
398- }
399- objs = append(objs, &obj)
400- }
401- for _, obj := range deployments {
402- obj := obj
403- objs = append(objs, &obj)
404- }
405- return &Plain{Objects: objs}, nil
406- }
407-
408- var PlainConverter = Converter{
409- BundleValidator: RegistryV1BundleValidator,
410- }
411-
412- const maxNameLength = 63
413-
414- func generateName(base string, o interface{}) (string, error) {
415- hashStr, err := util.DeepHashObject(o)
264+ objs, err := c.BundleRenderer.Render(rv1, installNamespace, targetNamespaces)
416265 if err != nil {
417- return "", err
418- }
419- if len(base)+len(hashStr) > maxNameLength {
420- base = base[:maxNameLength-len(hashStr)-1]
421- }
422-
423- return fmt.Sprintf("%s-%s", base, hashStr), nil
424- }
425-
426- func newServiceAccount(namespace, name string) corev1.ServiceAccount {
427- return corev1.ServiceAccount{
428- TypeMeta: metav1.TypeMeta{
429- Kind: "ServiceAccount",
430- APIVersion: corev1.SchemeGroupVersion.String(),
431- },
432- ObjectMeta: metav1.ObjectMeta{
433- Namespace: namespace,
434- Name: name,
435- },
436- }
437- }
438-
439- func newRole(namespace, name string, rules []rbacv1.PolicyRule) rbacv1.Role {
440- return rbacv1.Role{
441- TypeMeta: metav1.TypeMeta{
442- Kind: "Role",
443- APIVersion: rbacv1.SchemeGroupVersion.String(),
444- },
445- ObjectMeta: metav1.ObjectMeta{
446- Namespace: namespace,
447- Name: name,
448- },
449- Rules: rules,
450- }
451- }
452-
453- func newClusterRole(name string, rules []rbacv1.PolicyRule) rbacv1.ClusterRole {
454- return rbacv1.ClusterRole{
455- TypeMeta: metav1.TypeMeta{
456- Kind: "ClusterRole",
457- APIVersion: rbacv1.SchemeGroupVersion.String(),
458- },
459- ObjectMeta: metav1.ObjectMeta{
460- Name: name,
461- },
462- Rules: rules,
463- }
464- }
465-
466- func newRoleBinding(namespace, name, roleName, saNamespace string, saNames ...string) rbacv1.RoleBinding {
467- subjects := make([]rbacv1.Subject, 0, len(saNames))
468- for _, saName := range saNames {
469- subjects = append(subjects, rbacv1.Subject{
470- Kind: "ServiceAccount",
471- Namespace: saNamespace,
472- Name: saName,
473- })
474- }
475- return rbacv1.RoleBinding{
476- TypeMeta: metav1.TypeMeta{
477- Kind: "RoleBinding",
478- APIVersion: rbacv1.SchemeGroupVersion.String(),
479- },
480- ObjectMeta: metav1.ObjectMeta{
481- Namespace: namespace,
482- Name: name,
483- },
484- Subjects: subjects,
485- RoleRef: rbacv1.RoleRef{
486- APIGroup: rbacv1.GroupName,
487- Kind: "Role",
488- Name: roleName,
489- },
490- }
491- }
492-
493- func newClusterRoleBinding(name, roleName, saNamespace string, saNames ...string) rbacv1.ClusterRoleBinding {
494- subjects := make([]rbacv1.Subject, 0, len(saNames))
495- for _, saName := range saNames {
496- subjects = append(subjects, rbacv1.Subject{
497- Kind: "ServiceAccount",
498- Namespace: saNamespace,
499- Name: saName,
500- })
501- }
502- return rbacv1.ClusterRoleBinding{
503- TypeMeta: metav1.TypeMeta{
504- Kind: "ClusterRoleBinding",
505- APIVersion: rbacv1.SchemeGroupVersion.String(),
506- },
507- ObjectMeta: metav1.ObjectMeta{
508- Name: name,
509- },
510- Subjects: subjects,
511- RoleRef: rbacv1.RoleRef{
512- APIGroup: rbacv1.GroupName,
513- Kind: "ClusterRole",
514- Name: roleName,
515- },
266+ return nil, err
516267 }
268+ return &Plain{Objects: objs}, nil
517269}
0 commit comments