Skip to content

Commit 99a490a

Browse files
author
Per Goncalves da Silva
committed
update naming format
Signed-off-by: Per Goncalves da Silva <[email protected]>
1 parent 67ecf4b commit 99a490a

File tree

3 files changed

+127
-62
lines changed

3 files changed

+127
-62
lines changed

pkg/controller/operators/olm/operator_test.go

+25-13
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,18 @@ func NewFakeOperator(ctx context.Context, options ...fakeOperatorOption) (*Opera
298298
k8sClientFake.Resources = apiResourcesForObjects(append(config.extObjs, config.regObjs...))
299299
k8sClientFake.PrependReactor("*", "*", clienttesting.ReactionFunc(func(action clienttesting.Action) (bool, runtime.Object, error) {
300300
*config.actionLog = append(*config.actionLog, action)
301+
switch action.GetVerb() {
302+
case "create":
303+
a := action.(clienttesting.CreateAction)
304+
m := a.GetObject().(metav1.Object)
305+
306+
// create a name if generateName is set
307+
if m.GetGenerateName() != "" {
308+
m.SetName(m.GetGenerateName() + "xxxxx")
309+
m.SetGenerateName("")
310+
return false, a.GetObject(), nil
311+
}
312+
}
301313
return false, nil, nil
302314
}))
303315
apiextensionsFake := apiextensionsfake.NewSimpleClientset(config.extObjs...)
@@ -4554,7 +4566,7 @@ func TestSyncOperatorGroups(t *testing.T) {
45544566
&rbacv1.ClusterRole{
45554567
ObjectMeta: metav1.ObjectMeta{
45564568
ResourceVersion: "",
4557-
Name: "olm.operatorgroup.admin-d2ededg",
4569+
Name: "olm.og.operator-group-1.admin-xxxxx",
45584570
Labels: map[string]string{
45594571
"olm.owner": "operator-group-1",
45604572
"olm.owner.namespace": "operator-ns",
@@ -4565,7 +4577,7 @@ func TestSyncOperatorGroups(t *testing.T) {
45654577
&rbacv1.ClusterRole{
45664578
ObjectMeta: metav1.ObjectMeta{
45674579
ResourceVersion: "",
4568-
Name: "olm.operatorgroup.edit-d2ededg",
4580+
Name: "olm.og.operator-group-1.edit-xxxxx",
45694581
Labels: map[string]string{
45704582
"olm.owner": "operator-group-1",
45714583
"olm.owner.namespace": "operator-ns",
@@ -4576,7 +4588,7 @@ func TestSyncOperatorGroups(t *testing.T) {
45764588
&rbacv1.ClusterRole{
45774589
ObjectMeta: metav1.ObjectMeta{
45784590
ResourceVersion: "",
4579-
Name: "olm.operatorgroup.view-d2ededg",
4591+
Name: "olm.og.operator-group-1.view-xxxxx",
45804592
Labels: map[string]string{
45814593
"olm.owner": "operator-group-1",
45824594
"olm.owner.namespace": "operator-ns",
@@ -4589,7 +4601,7 @@ func TestSyncOperatorGroups(t *testing.T) {
45894601
},
45904602
{
45914603
// check that even if cluster roles exist without the naming convention, we create the new ones and leave the old ones unchanged
4592-
name: "MatchingNamespace/NoCSVs/UpdatesOldClusterRoles",
4604+
name: "MatchingNamespace/NoCSVs/KeepOldClusterRoles",
45934605
expectedEqual: true,
45944606
initial: initial{
45954607
operatorGroup: &operatorsv1.OperatorGroup{
@@ -4658,7 +4670,7 @@ func TestSyncOperatorGroups(t *testing.T) {
46584670
&rbacv1.ClusterRole{
46594671
ObjectMeta: metav1.ObjectMeta{
46604672
ResourceVersion: "",
4661-
Name: "olm.operatorgroup.admin-d2ededg",
4673+
Name: "olm.og.operator-group-1.admin-xxxxx",
46624674
Labels: map[string]string{
46634675
"olm.owner": "operator-group-1",
46644676
"olm.owner.namespace": "operator-ns",
@@ -4669,7 +4681,7 @@ func TestSyncOperatorGroups(t *testing.T) {
46694681
&rbacv1.ClusterRole{
46704682
ObjectMeta: metav1.ObjectMeta{
46714683
ResourceVersion: "",
4672-
Name: "olm.operatorgroup.edit-d2ededg",
4684+
Name: "olm.og.operator-group-1.edit-xxxxx",
46734685
Labels: map[string]string{
46744686
"olm.owner": "operator-group-1",
46754687
"olm.owner.namespace": "operator-ns",
@@ -4680,7 +4692,7 @@ func TestSyncOperatorGroups(t *testing.T) {
46804692
&rbacv1.ClusterRole{
46814693
ObjectMeta: metav1.ObjectMeta{
46824694
ResourceVersion: "",
4683-
Name: "olm.operatorgroup.view-d2ededg",
4695+
Name: "olm.og.operator-group-1.view-xxxxx",
46844696
Labels: map[string]string{
46854697
"olm.owner": "operator-group-1",
46864698
"olm.owner.namespace": "operator-ns",
@@ -4754,7 +4766,7 @@ func TestSyncOperatorGroups(t *testing.T) {
47544766
&rbacv1.ClusterRole{
47554767
ObjectMeta: metav1.ObjectMeta{
47564768
ResourceVersion: "",
4757-
Name: "olm.operatorgroup.admin-xxxxx",
4769+
Name: "olm.og.operator-group-1.admin-xxxxx",
47584770
Labels: map[string]string{
47594771
"olm.owner": "operator-group-1",
47604772
"olm.owner.namespace": "operator-ns",
@@ -4765,7 +4777,7 @@ func TestSyncOperatorGroups(t *testing.T) {
47654777
&rbacv1.ClusterRole{
47664778
ObjectMeta: metav1.ObjectMeta{
47674779
ResourceVersion: "",
4768-
Name: "olm.operatorgroup.edit-yyyyy",
4780+
Name: "olm.og.operator-group-1.edit-xxxxx",
47694781
Labels: map[string]string{
47704782
"olm.owner": "operator-group-1",
47714783
"olm.owner.namespace": "operator-ns",
@@ -4776,7 +4788,7 @@ func TestSyncOperatorGroups(t *testing.T) {
47764788
&rbacv1.ClusterRole{
47774789
ObjectMeta: metav1.ObjectMeta{
47784790
ResourceVersion: "",
4779-
Name: "olm.operatorgroup.view-zzzzz",
4791+
Name: "olm.og.operator-group-1.view-xxxxx",
47804792
Labels: map[string]string{
47814793
"olm.owner": "operator-group-1",
47824794
"olm.owner.namespace": "operator-ns",
@@ -4794,7 +4806,7 @@ func TestSyncOperatorGroups(t *testing.T) {
47944806
&rbacv1.ClusterRole{
47954807
ObjectMeta: metav1.ObjectMeta{
47964808
ResourceVersion: "",
4797-
Name: "olm.operatorgroup.admin-xxxxx",
4809+
Name: "olm.og.operator-group-1.admin-xxxxx",
47984810
Labels: map[string]string{
47994811
"olm.owner": "operator-group-1",
48004812
"olm.owner.namespace": "operator-ns",
@@ -4805,7 +4817,7 @@ func TestSyncOperatorGroups(t *testing.T) {
48054817
&rbacv1.ClusterRole{
48064818
ObjectMeta: metav1.ObjectMeta{
48074819
ResourceVersion: "",
4808-
Name: "olm.operatorgroup.edit-yyyyy",
4820+
Name: "olm.og.operator-group-1.edit-xxxxx",
48094821
Labels: map[string]string{
48104822
"olm.owner": "operator-group-1",
48114823
"olm.owner.namespace": "operator-ns",
@@ -4816,7 +4828,7 @@ func TestSyncOperatorGroups(t *testing.T) {
48164828
&rbacv1.ClusterRole{
48174829
ObjectMeta: metav1.ObjectMeta{
48184830
ResourceVersion: "",
4819-
Name: "olm.operatorgroup.view-zzzzz",
4831+
Name: "olm.og.operator-group-1.view-xxxxx",
48204832
Labels: map[string]string{
48214833
"olm.owner": "operator-group-1",
48224834
"olm.owner.namespace": "operator-ns",

pkg/controller/operators/olm/operatorgroup.go

+97-48
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"context"
55
"fmt"
66
"hash/fnv"
7-
"math/rand"
7+
"math"
88
"reflect"
9+
"regexp"
10+
"sort"
911
"strings"
1012

1113
"github.com/sirupsen/logrus"
@@ -35,6 +37,12 @@ const (
3537
AdminSuffix = "admin"
3638
EditSuffix = "edit"
3739
ViewSuffix = "view"
40+
41+
// kubeResourceNameLimit is the maximum length of a Kubernetes resource name
42+
kubeResourceNameLimit = 253
43+
44+
// resourceRandomPrefixLengh is the length of the random prefix added to the resource name
45+
resourceRandomPrefixLength = 5
3846
)
3947

4048
var (
@@ -986,46 +994,30 @@ func (a *Operator) updateNamespaceList(op *operatorsv1.OperatorGroup) ([]string,
986994
return namespaceList, nil
987995
}
988996

989-
func (a *Operator) stableRand(op *operatorsv1.OperatorGroup) (string, error) {
990-
key := fmt.Sprintf("%s/%s", op.GetNamespace(), op.GetName())
991-
h := fnv.New64a()
992-
_, err := h.Write([]byte(key))
993-
if err != nil {
994-
return "", err
995-
}
996-
rnd := rand.NewSource(int64(h.Sum64()))
997-
const alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"
997+
func (a *Operator) ensureOpGroupClusterRole(op *operatorsv1.OperatorGroup, roleType string, apis cache.APISet) error {
998+
// create target cluster role spec
999+
var clusterRole *rbacv1.ClusterRole
9981000

999-
b := make([]byte, 7)
1000-
for i := range b {
1001-
b[i] = alphabet[rnd.Int63()%int64(len(alphabet))]
1002-
}
1003-
return string(b), nil
1004-
}
1001+
// to respect kubernetes resource length limitations we need to truncate the operator group name
1002+
template := "olm.og.%s.%s-" // final name will look like, e.g. olm.og.my-og.admin-pll5s
1003+
numTemplateChars := len(strings.Replace(template, "%s", "", -1))
1004+
roleTypeLength := len(roleType)
10051005

1006-
func (a *Operator) getClusterRoleName(op *operatorsv1.OperatorGroup, roleType string) (string, error) {
1007-
roleSuffix, err := a.stableRand(op)
1008-
if err != nil {
1009-
return "", err
1010-
}
1011-
return fmt.Sprintf("olm.operatorgroup.%s-%s", roleType, roleSuffix), nil
1012-
}
1006+
// the operator group component of the name must be limited by the resource name length limit
1007+
// minus the number of characters used up by the other components of the name
1008+
nameLimit := kubeResourceNameLimit - numTemplateChars - roleTypeLength - resourceRandomPrefixLength
1009+
operatorGroupName := op.GetName()[:int(math.Min(float64(nameLimit), float64(len(op.GetName()))))]
10131010

1014-
func (a *Operator) ensureOpGroupClusterRole(op *operatorsv1.OperatorGroup, suffix string, apis cache.APISet) error {
1015-
// create target cluster role spec
1016-
var clusterRole *rbacv1.ClusterRole
1017-
clusterRoleName, err := a.getClusterRoleName(op, suffix)
1018-
if err != nil {
1019-
return err
1020-
}
1021-
aggregationRule, err := a.getClusterRoleAggregationRule(apis, suffix)
1011+
clusterRoleName := fmt.Sprintf(template, operatorGroupName, roleType)
1012+
aggregationRule, err := a.getClusterRoleAggregationRule(apis, roleType)
10221013
if err != nil {
10231014
return err
10241015
}
10251016

1017+
// if we'll be creating a fresh cluster role, use generateName to avoid name collisions
10261018
clusterRole = &rbacv1.ClusterRole{
10271019
ObjectMeta: metav1.ObjectMeta{
1028-
Name: clusterRoleName,
1020+
GenerateName: clusterRoleName,
10291021
},
10301022
AggregationRule: aggregationRule,
10311023
}
@@ -1034,29 +1026,74 @@ func (a *Operator) ensureOpGroupClusterRole(op *operatorsv1.OperatorGroup, suffi
10341026
return err
10351027
}
10361028

1037-
// get existing cluster role for this level (suffix: admin, edit, view))
1038-
existingRole, err := a.lister.RbacV1().ClusterRoleLister().Get(clusterRoleName)
1039-
if err != nil && !apierrors.IsNotFound(err) {
1029+
// list current cluster roles owned by this operator group
1030+
clusterRoles, err := a.lister.RbacV1().ClusterRoleLister().List(ownerutil.OperatorGroupOwnerSelector(op))
1031+
if err != nil {
10401032
return err
10411033
}
1042-
if existingRole != nil && existingRole.Name == clusterRoleName {
1043-
// if the cluster role already exists, check if it needs to be updated
1044-
if labels.Equals(existingRole.Labels, clusterRole.Labels) && reflect.DeepEqual(existingRole.AggregationRule, aggregationRule) {
1045-
return nil
1034+
1035+
// find the cluster role that matches the template and is of the correct type
1036+
var existingClusterRoles []*rbacv1.ClusterRole
1037+
re, ok := ogClusterRoleNameRegExMap[roleType]
1038+
if !ok {
1039+
return fmt.Errorf("no regex found for role type: %s", roleType)
1040+
}
1041+
for _, cr := range clusterRoles {
1042+
if re.FindStringSubmatch(cr.GetName()) != nil {
1043+
existingClusterRoles = append(existingClusterRoles, cr)
10461044
}
1047-
// if skew was found, correct it
1048-
if _, err := a.opClient.UpdateClusterRole(clusterRole); err != nil {
1049-
a.logger.WithError(err).Errorf("Update existing cluster role failed: %v", clusterRole)
1045+
}
1046+
1047+
switch len(existingClusterRoles) {
1048+
// if no cluster role exists, create one
1049+
case 0:
1050+
a.logger.Infof("Creating cluster role: %s owned by operator group: %s/%s", clusterRole.GetGenerateName(), op.GetNamespace(), op.GetName())
1051+
if _, err = a.opClient.KubernetesInterface().RbacV1().ClusterRoles().Create(context.TODO(), clusterRole, metav1.CreateOptions{}); err != nil {
1052+
// name collision, try again with a new name in the next reconcile
1053+
a.logger.WithError(err).Errorf("Create cluster role failed: %v", clusterRole)
10501054
}
10511055
return err
1052-
}
1056+
// if an existing cluster role resource is found, update it if necessary
1057+
case 1:
1058+
existingClusterRole := existingClusterRoles[0].DeepCopy()
1059+
// the cluster role will need to be updated if the aggregation rules have changed - otherwise, nothing to do
1060+
// the resource is guaranteed to have the correct ownership labels because we reached this part of the code
1061+
if !reflect.DeepEqual(existingClusterRole.AggregationRule, aggregationRule) {
1062+
if _, err := a.opClient.UpdateClusterRole(existingClusterRole); err != nil {
1063+
a.logger.WithError(err).Errorf("Update existing cluster role failed: %v", clusterRole)
1064+
}
1065+
return err
1066+
}
1067+
// the inherent race condition created by listing to check if a resource exists and then creating it
1068+
// and the fact that we are using generateName means that it is possible for multiple cluster roles of the same
1069+
// role type to be created for the same operator group. In this case, we'll disambiguate by keeping the oldest
1070+
// cluster role (by creation timestamp - tie-breaking by name) and deleting the others
1071+
default:
1072+
a.logger.Warnf("multiple (%d) cluster roles of type %s owned by operator group %s/%s found", len(existingClusterRoles), roleType, op.GetNamespace(), op.GetName())
1073+
// sort by creation timestamp and tie-break by name
1074+
sort.Slice(existingClusterRoles, func(i, j int) bool {
1075+
creationTimeI := existingClusterRoles[i].GetCreationTimestamp()
1076+
creationTimeJ := existingClusterRoles[j].GetCreationTimestamp()
1077+
if creationTimeI.Equal(&creationTimeJ) {
1078+
return strings.Compare(existingClusterRoles[i].GetName(), existingClusterRoles[j].GetName()) < 0
1079+
}
1080+
return creationTimeI.Before(&creationTimeJ)
1081+
})
1082+
1083+
// delete all but the oldest cluster role
1084+
a.logger.Infof("keeping cluster role: %s owned by operator group: %s/%s and deleting %d others", existingClusterRoles[0].GetName(), op.GetNamespace(), op.GetName(), len(existingClusterRoles)-1)
1085+
for _, cr := range existingClusterRoles[1:] {
1086+
a.logger.Infof("deleting cluster role: %s owned by operator group: %s/%s - there may be only one", cr.GetName(), op.GetNamespace(), op.GetName())
1087+
if err := a.opClient.DeleteClusterRole(cr.GetName(), &metav1.DeleteOptions{}); err != nil {
1088+
a.logger.WithError(err).Errorf("Delete cluster role failed: %v", cr)
1089+
return err
1090+
}
1091+
}
10531092

1054-
a.logger.Infof("Creating cluster role: %s owned by operator group: %s/%s", clusterRole.GetName(), op.GetNamespace(), op.GetName())
1055-
if _, err = a.opClient.KubernetesInterface().RbacV1().ClusterRoles().Create(context.TODO(), clusterRole, metav1.CreateOptions{}); err != nil {
1056-
// name collision, try again with a new name in the next reconcile
1057-
a.logger.WithError(err).Errorf("Create cluster role failed: %v", clusterRole)
1093+
// now that we're (hopefully) down to one cluster role, re-reconcile
1094+
return fmt.Errorf("multiple cluster roles of type %s owned by operator group %s/%s found", roleType, op.GetNamespace(), op.GetName())
10581095
}
1059-
return err
1096+
return nil
10601097
}
10611098

10621099
func (a *Operator) getClusterRoleAggregationRule(apis cache.APISet, suffix string) (*rbacv1.AggregationRule, error) {
@@ -1174,3 +1211,15 @@ func csvCopyPrototype(src, dst *v1alpha1.ClusterServiceVersion) {
11741211
dst.Status.Reason = v1alpha1.CSVReasonCopied
11751212
dst.Status.Message = fmt.Sprintf("The operator is running in %s but is managing this namespace", src.GetNamespace())
11761213
}
1214+
1215+
// ogClusterRoleNameRegEx returns a regexp for the naming format used for cluster roles owned by operator groups
1216+
// for a particular role type e.g. olm.og.my-og.admin-pll2k (where pll2k is a random suffix and admin is the role type)
1217+
var ogClusterRoleNameRegExMap = map[string]*regexp.Regexp{
1218+
"admin": ogClusterRoleNameRegEx("admin"),
1219+
"edit": ogClusterRoleNameRegEx("edit"),
1220+
"view": ogClusterRoleNameRegEx("view"),
1221+
}
1222+
1223+
func ogClusterRoleNameRegEx(roleType string) *regexp.Regexp {
1224+
return regexp.MustCompile(fmt.Sprintf(`^olm\.og\.[^\.]+\.%s-[a-z0-9]{5}$`, roleType))
1225+
}

pkg/lib/ownerutil/util.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package ownerutil
22

33
import (
44
"fmt"
5-
65
log "github.com/sirupsen/logrus"
76
corev1 "k8s.io/api/core/v1"
87
rbac "k8s.io/api/rbac/v1"
@@ -271,6 +270,11 @@ func CSVOwnerSelector(owner *operatorsv1alpha1.ClusterServiceVersion) labels.Sel
271270
return labels.SelectorFromSet(OwnerLabel(owner, operatorsv1alpha1.ClusterServiceVersionKind))
272271
}
273272

273+
// OperatorGroupOwnerSelector returns a label selector to find generated objects owned by owner
274+
func OperatorGroupOwnerSelector(owner *operatorsv1.OperatorGroup) labels.Selector {
275+
return labels.SelectorFromSet(OwnerLabel(owner, "OperatorGroup"))
276+
}
277+
274278
// AddOwner adds an owner to the ownerref list.
275279
func AddOwner(object metav1.Object, owner Owner, blockOwnerDeletion, isController bool) {
276280
// Most of the time we won't have TypeMeta on the object, so we infer it for types we know about

0 commit comments

Comments
 (0)