k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/controller/clusterroleaggregation/clusterroleaggregation_controller_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package clusterroleaggregation 18 19 import ( 20 "context" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 rbacv1 "k8s.io/api/rbac/v1" 25 "k8s.io/apimachinery/pkg/api/equality" 26 "k8s.io/apimachinery/pkg/api/errors" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/apimachinery/pkg/runtime" 29 "k8s.io/apimachinery/pkg/util/yaml" 30 rbacv1ac "k8s.io/client-go/applyconfigurations/rbac/v1" 31 fakeclient "k8s.io/client-go/kubernetes/fake" 32 rbaclisters "k8s.io/client-go/listers/rbac/v1" 33 clienttesting "k8s.io/client-go/testing" 34 "k8s.io/client-go/tools/cache" 35 36 "k8s.io/kubernetes/pkg/controller" 37 ) 38 39 func TestSyncClusterRole(t *testing.T) { 40 hammerRules := func() []rbacv1.PolicyRule { 41 return []rbacv1.PolicyRule{ 42 {Verbs: []string{"hammer"}, Resources: []string{"nails"}}, 43 {Verbs: []string{"hammer"}, Resources: []string{"wedges"}}, 44 } 45 } 46 chiselRules := func() []rbacv1.PolicyRule { 47 return []rbacv1.PolicyRule{ 48 {Verbs: []string{"chisel"}, Resources: []string{"mortises"}}, 49 } 50 } 51 sawRules := func() []rbacv1.PolicyRule { 52 return []rbacv1.PolicyRule{ 53 {Verbs: []string{"saw"}, Resources: []string{"boards"}}, 54 } 55 } 56 role := func(name string, labels map[string]string, rules []rbacv1.PolicyRule) *rbacv1.ClusterRole { 57 return &rbacv1.ClusterRole{ 58 ObjectMeta: metav1.ObjectMeta{Name: name, Labels: labels}, 59 Rules: rules, 60 } 61 } 62 combinedRole := func(selectors []map[string]string, rules ...[]rbacv1.PolicyRule) *rbacv1.ClusterRole { 63 ret := &rbacv1.ClusterRole{ 64 ObjectMeta: metav1.ObjectMeta{Name: "combined"}, 65 AggregationRule: &rbacv1.AggregationRule{}, 66 } 67 for _, selector := range selectors { 68 ret.AggregationRule.ClusterRoleSelectors = append(ret.AggregationRule.ClusterRoleSelectors, 69 metav1.LabelSelector{MatchLabels: selector}) 70 } 71 for _, currRules := range rules { 72 ret.Rules = append(ret.Rules, currRules...) 73 } 74 return ret 75 } 76 77 combinedApplyRole := func(selectors []map[string]string, rules ...[]rbacv1.PolicyRule) *rbacv1ac.ClusterRoleApplyConfiguration { 78 ret := rbacv1ac.ClusterRole("combined") 79 80 var r []*rbacv1ac.PolicyRuleApplyConfiguration 81 for _, currRules := range rules { 82 r = append(r, toApplyPolicyRules(currRules)...) 83 } 84 ret.WithRules(r...) 85 return ret 86 } 87 88 tests := []struct { 89 name string 90 startingClusterRoles []*rbacv1.ClusterRole 91 clusterRoleToSync string 92 expectedClusterRole *rbacv1.ClusterRole 93 expectedClusterRoleApply *rbacv1ac.ClusterRoleApplyConfiguration 94 }{ 95 { 96 name: "remove dead rules", 97 startingClusterRoles: []*rbacv1.ClusterRole{ 98 role("hammer", map[string]string{"foo": "bar"}, hammerRules()), 99 combinedRole([]map[string]string{{"foo": "bar"}}, sawRules()), 100 }, 101 clusterRoleToSync: "combined", 102 expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}}, hammerRules()), 103 expectedClusterRoleApply: combinedApplyRole([]map[string]string{{"foo": "bar"}}, hammerRules()), 104 }, 105 { 106 name: "strip rules", 107 startingClusterRoles: []*rbacv1.ClusterRole{ 108 role("hammer", map[string]string{"foo": "not-bar"}, hammerRules()), 109 combinedRole([]map[string]string{{"foo": "bar"}}, hammerRules()), 110 }, 111 clusterRoleToSync: "combined", 112 expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}}), 113 expectedClusterRoleApply: combinedApplyRole([]map[string]string{{"foo": "bar"}}), 114 }, 115 { 116 name: "select properly and put in order", 117 startingClusterRoles: []*rbacv1.ClusterRole{ 118 role("hammer", map[string]string{"foo": "bar"}, hammerRules()), 119 role("chisel", map[string]string{"foo": "bar"}, chiselRules()), 120 role("saw", map[string]string{"foo": "not-bar"}, sawRules()), 121 combinedRole([]map[string]string{{"foo": "bar"}}), 122 }, 123 clusterRoleToSync: "combined", 124 expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}}, chiselRules(), hammerRules()), 125 expectedClusterRoleApply: combinedApplyRole([]map[string]string{{"foo": "bar"}}, chiselRules(), hammerRules()), 126 }, 127 { 128 name: "select properly with multiple selectors", 129 startingClusterRoles: []*rbacv1.ClusterRole{ 130 role("hammer", map[string]string{"foo": "bar"}, hammerRules()), 131 role("chisel", map[string]string{"foo": "bar"}, chiselRules()), 132 role("saw", map[string]string{"foo": "not-bar"}, sawRules()), 133 combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}), 134 }, 135 clusterRoleToSync: "combined", 136 expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}, chiselRules(), hammerRules(), sawRules()), 137 expectedClusterRoleApply: combinedApplyRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}, chiselRules(), hammerRules(), sawRules()), 138 }, 139 { 140 name: "select properly remove duplicates", 141 startingClusterRoles: []*rbacv1.ClusterRole{ 142 role("hammer", map[string]string{"foo": "bar"}, hammerRules()), 143 role("chisel", map[string]string{"foo": "bar"}, chiselRules()), 144 role("saw", map[string]string{"foo": "bar"}, sawRules()), 145 role("other-saw", map[string]string{"foo": "not-bar"}, sawRules()), 146 combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}), 147 }, 148 clusterRoleToSync: "combined", 149 expectedClusterRole: combinedRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}, chiselRules(), hammerRules(), sawRules()), 150 expectedClusterRoleApply: combinedApplyRole([]map[string]string{{"foo": "bar"}, {"foo": "not-bar"}}, chiselRules(), hammerRules(), sawRules()), 151 }, 152 { 153 name: "no diff skip", 154 startingClusterRoles: []*rbacv1.ClusterRole{ 155 role("hammer", map[string]string{"foo": "bar"}, hammerRules()), 156 combinedRole([]map[string]string{{"foo": "bar"}}, hammerRules()), 157 }, 158 clusterRoleToSync: "combined", 159 expectedClusterRoleApply: nil, 160 }} 161 162 for _, serverSideApplyEnabled := range []bool{true, false} { 163 for _, test := range tests { 164 t.Run(test.name, func(t *testing.T) { 165 indexer := cache.NewIndexer(controller.KeyFunc, cache.Indexers{}) 166 objs := []runtime.Object{} 167 for _, obj := range test.startingClusterRoles { 168 objs = append(objs, obj) 169 indexer.Add(obj) 170 } 171 fakeClient := fakeclient.NewSimpleClientset(objs...) 172 173 // The default reactor doesn't support apply, so we need our own (trivial) reactor 174 fakeClient.PrependReactor("patch", "clusterroles", func(action clienttesting.Action) (handled bool, ret runtime.Object, err error) { 175 if serverSideApplyEnabled == false { 176 // UnsupportedMediaType 177 return true, nil, errors.NewGenericServerResponse(415, "get", action.GetResource().GroupResource(), "test", "Apply not supported", 0, true) 178 } 179 return true, nil, nil // clusterroleaggregator drops returned objects so no point in constructing them 180 }) 181 c := ClusterRoleAggregationController{ 182 clusterRoleClient: fakeClient.RbacV1(), 183 clusterRoleLister: rbaclisters.NewClusterRoleLister(indexer), 184 } 185 err := c.syncClusterRole(context.TODO(), test.clusterRoleToSync) 186 if err != nil { 187 t.Fatal(err) 188 } 189 190 if test.expectedClusterRoleApply == nil { 191 if len(fakeClient.Actions()) != 0 { 192 t.Fatalf("unexpected actions %#v", fakeClient.Actions()) 193 } 194 return 195 } 196 197 expectedActions := 1 198 if !serverSideApplyEnabled { 199 expectedActions = 2 200 } 201 if len(fakeClient.Actions()) != expectedActions { 202 t.Fatalf("unexpected actions %#v", fakeClient.Actions()) 203 } 204 205 action := fakeClient.Actions()[0] 206 if !action.Matches("patch", "clusterroles") { 207 t.Fatalf("unexpected action %#v", action) 208 } 209 applyAction, ok := action.(clienttesting.PatchAction) 210 if !ok { 211 t.Fatalf("unexpected action %#v", action) 212 } 213 ac := &rbacv1ac.ClusterRoleApplyConfiguration{} 214 if err := yaml.Unmarshal(applyAction.GetPatch(), ac); err != nil { 215 t.Fatalf("error unmarshalling apply request: %v", err) 216 } 217 if !equality.Semantic.DeepEqual(ac, test.expectedClusterRoleApply) { 218 t.Fatalf("%v", cmp.Diff(test.expectedClusterRoleApply, ac)) 219 } 220 if expectedActions == 2 { 221 action := fakeClient.Actions()[1] 222 if !action.Matches("update", "clusterroles") { 223 t.Fatalf("unexpected action %#v", action) 224 } 225 updateAction, ok := action.(clienttesting.UpdateAction) 226 if !ok { 227 t.Fatalf("unexpected action %#v", action) 228 } 229 if !equality.Semantic.DeepEqual(updateAction.GetObject().(*rbacv1.ClusterRole), test.expectedClusterRole) { 230 t.Fatalf("%v", cmp.Diff(test.expectedClusterRole, updateAction.GetObject().(*rbacv1.ClusterRole))) 231 } 232 } 233 }) 234 } 235 } 236 }