Skip to content

Commit fce8174

Browse files
committed
operatorgroup: ensure clusterroleselectors in clusterrole aggregation rules are sorted
Signed-off-by: Joe Lanford <[email protected]>
1 parent e1f5f6b commit fce8174

File tree

2 files changed

+89
-9
lines changed

2 files changed

+89
-9
lines changed

pkg/controller/operators/olm/operatorgroup.go

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/sha256"
66
"fmt"
7+
"k8s.io/apimachinery/pkg/util/sets"
78
"math/big"
89
"reflect"
910
"strings"
@@ -1047,24 +1048,35 @@ func (a *Operator) ensureOpGroupClusterRole(op *operatorsv1.OperatorGroup, suffi
10471048
}
10481049

10491050
func (a *Operator) getClusterRoleAggregationRule(apis cache.APISet, suffix string) (*rbacv1.AggregationRule, error) {
1050-
var selectors []metav1.LabelSelector
1051+
if len(apis) == 0 {
1052+
return nil, nil
1053+
}
1054+
1055+
aggregationLabels := sets.New[string]()
10511056
for api := range apis {
10521057
aggregationLabel, err := aggregationLabelFromAPIKey(api, suffix)
10531058
if err != nil {
10541059
return nil, err
10551060
}
1061+
aggregationLabels.Insert(aggregationLabel)
1062+
}
1063+
1064+
// The order of the resulting selectors MUST BE deterministic in order
1065+
// to avoid unnecessary writes against the API server where only the order
1066+
// is changing. Therefore, we use `sets.List` to iterate. It returns a
1067+
// sorted slice of the aggregation labels.
1068+
selectors := make([]metav1.LabelSelector, 0, aggregationLabels.Len())
1069+
for _, aggregationLabel := range sets.List(aggregationLabels) {
10561070
selectors = append(selectors, metav1.LabelSelector{
10571071
MatchLabels: map[string]string{
10581072
aggregationLabel: "true",
10591073
},
10601074
})
10611075
}
1062-
if len(selectors) > 0 {
1063-
return &rbacv1.AggregationRule{
1064-
ClusterRoleSelectors: selectors,
1065-
}, nil
1066-
}
1067-
return nil, nil
1076+
1077+
return &rbacv1.AggregationRule{
1078+
ClusterRoleSelectors: selectors,
1079+
}, nil
10681080
}
10691081

10701082
func (a *Operator) ensureOpGroupClusterRoles(op *operatorsv1.OperatorGroup, apis cache.APISet) error {

pkg/controller/operators/olm/operatorgroup_test.go

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ import (
88
"github.com/sirupsen/logrus/hooks/test"
99
"github.com/stretchr/testify/assert"
1010
"github.com/stretchr/testify/require"
11-
"k8s.io/client-go/metadata/metadatalister"
12-
11+
rbacv1 "k8s.io/api/rbac/v1"
1312
"k8s.io/apimachinery/pkg/api/errors"
1413
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1514
"k8s.io/apimachinery/pkg/labels"
15+
"k8s.io/client-go/metadata/metadatalister"
1616
ktesting "k8s.io/client-go/testing"
1717

1818
"github.com/operator-framework/api/pkg/operators/v1alpha1"
1919
"github.com/operator-framework/operator-lifecycle-manager/pkg/api/client/clientset/versioned/fake"
20+
"github.com/operator-framework/operator-lifecycle-manager/pkg/controller/registry/resolver/cache"
21+
"github.com/operator-framework/operator-registry/pkg/registry"
2022
)
2123

2224
func TestCopyToNamespace(t *testing.T) {
@@ -407,3 +409,69 @@ func TestCSVCopyPrototype(t *testing.T) {
407409
},
408410
}, dst)
409411
}
412+
413+
func TestOperator_getClusterRoleAggregationRule(t *testing.T) {
414+
type args struct {
415+
apis cache.APISet
416+
suffix string
417+
}
418+
tests := []struct {
419+
name string
420+
apis cache.APISet
421+
suffix string
422+
want func(*testing.T, *rbacv1.AggregationRule)
423+
wantErr require.ErrorAssertionFunc
424+
}{
425+
{
426+
name: "no aggregation rule when no APIs",
427+
apis: cache.APISet{},
428+
suffix: "admin",
429+
want: func(t *testing.T, rule *rbacv1.AggregationRule) {
430+
require.Nil(t, rule)
431+
},
432+
wantErr: require.NoError,
433+
},
434+
{
435+
name: "ordered selectors in aggregation rule",
436+
apis: cache.APISet{
437+
registry.APIKey{Group: "example.com", Version: "v1alpha1", Kind: "Foo"}: {},
438+
registry.APIKey{Group: "example.com", Version: "v1alpha2", Kind: "Foo"}: {},
439+
registry.APIKey{Group: "example.com", Version: "v1alpha3", Kind: "Foo"}: {},
440+
registry.APIKey{Group: "example.com", Version: "v1alpha4", Kind: "Foo"}: {},
441+
registry.APIKey{Group: "example.com", Version: "v1alpha5", Kind: "Foo"}: {},
442+
registry.APIKey{Group: "example.com", Version: "v1alpha1", Kind: "Bar"}: {},
443+
registry.APIKey{Group: "example.com", Version: "v1alpha2", Kind: "Bar"}: {},
444+
registry.APIKey{Group: "example.com", Version: "v1alpha3", Kind: "Bar"}: {},
445+
registry.APIKey{Group: "example.com", Version: "v1alpha4", Kind: "Bar"}: {},
446+
registry.APIKey{Group: "example.com", Version: "v1alpha5", Kind: "Bar"}: {},
447+
},
448+
suffix: "admin",
449+
want: func(t *testing.T, rule *rbacv1.AggregationRule) {
450+
getOneKey := func(t *testing.T, m map[string]string) string {
451+
require.Len(t, m, 1)
452+
for k := range m {
453+
return k
454+
}
455+
t.Fatalf("no keys found in map")
456+
return ""
457+
}
458+
459+
a := getOneKey(t, rule.ClusterRoleSelectors[0].MatchLabels)
460+
for _, selector := range rule.ClusterRoleSelectors[1:] {
461+
b := getOneKey(t, selector.MatchLabels)
462+
require.Lessf(t, a, b, "expected selector match labels keys to be in sorted ascending order")
463+
a = b
464+
}
465+
},
466+
wantErr: require.NoError,
467+
},
468+
}
469+
for _, tt := range tests {
470+
t.Run(tt.name, func(t *testing.T) {
471+
a := &Operator{}
472+
got, err := a.getClusterRoleAggregationRule(tt.apis, tt.suffix)
473+
tt.wantErr(t, err)
474+
tt.want(t, got)
475+
})
476+
}
477+
}

0 commit comments

Comments
 (0)