knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/configmaps/table_test.go (about) 1 /* 2 Copyright 2019 The Knative 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 configmaps 18 19 import ( 20 "context" 21 "reflect" 22 "testing" 23 "time" 24 25 kubeclient "knative.dev/pkg/client/injection/kube/client/fake" 26 _ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake" 27 pkgreconciler "knative.dev/pkg/reconciler" 28 29 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 30 corev1 "k8s.io/api/core/v1" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/apimachinery/pkg/util/wait" 35 clientgotesting "k8s.io/client-go/testing" 36 "knative.dev/pkg/configmap" 37 "knative.dev/pkg/controller" 38 "knative.dev/pkg/ptr" 39 "knative.dev/pkg/system" 40 "knative.dev/pkg/webhook" 41 certresources "knative.dev/pkg/webhook/certificates/resources" 42 43 . "knative.dev/pkg/reconciler/testing" 44 . "knative.dev/pkg/webhook/testing" 45 ) 46 47 func TestReconcile(t *testing.T) { 48 name, path := "foo.bar.baz", "/blah" 49 secretName := "webhook-secret" 50 51 secret := &corev1.Secret{ 52 ObjectMeta: metav1.ObjectMeta{ 53 Name: secretName, 54 Namespace: system.Namespace(), 55 }, 56 Data: map[string][]byte{ 57 certresources.ServerKey: []byte("present"), 58 certresources.ServerCert: []byte("present"), 59 certresources.CACert: []byte("present"), 60 }, 61 } 62 63 ns := &corev1.Namespace{ 64 ObjectMeta: metav1.ObjectMeta{ 65 Name: system.Namespace(), 66 }, 67 } 68 nsRef := *metav1.NewControllerRef(ns, corev1.SchemeGroupVersion.WithKind("Namespace")) 69 nsRef.Controller = ptr.Bool(false) 70 expectedOwnerReferences := []metav1.OwnerReference{nsRef} 71 72 ruleScope := admissionregistrationv1.NamespacedScope 73 74 // These are the rules we expect given the context of "validations". 75 expectedRules := []admissionregistrationv1.RuleWithOperations{{ 76 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 77 Rule: admissionregistrationv1.Rule{ 78 APIGroups: []string{""}, 79 APIVersions: []string{"v1"}, 80 Resources: []string{"configmaps/*"}, 81 Scope: &ruleScope, 82 }, 83 }} 84 85 // The key to use, which for this singleton reconciler doesn't matter (although the 86 // namespace matters for namespace validation). 87 key := system.Namespace() + "/does not matter" 88 89 table := TableTest{{ 90 Name: "no secret", 91 Key: key, 92 WantErr: true, 93 }, { 94 Name: "secret missing CA Cert", 95 Key: key, 96 Objects: []runtime.Object{&corev1.Secret{ 97 ObjectMeta: metav1.ObjectMeta{ 98 Name: secretName, 99 Namespace: system.Namespace(), 100 }, 101 Data: map[string][]byte{ 102 certresources.ServerKey: []byte("present"), 103 certresources.ServerCert: []byte("present"), 104 // certresources.CACert: []byte("missing"), 105 }, 106 }}, 107 WantErr: true, 108 }, { 109 Name: "secret exists, but VWH does not", 110 Key: key, 111 Objects: []runtime.Object{secret}, 112 WantErr: true, 113 }, { 114 Name: "secret and VWH exist, missing service reference", 115 Key: key, 116 Objects: []runtime.Object{ 117 secret, ns, 118 &admissionregistrationv1.ValidatingWebhookConfiguration{ 119 ObjectMeta: metav1.ObjectMeta{ 120 Name: name, 121 }, 122 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 123 Name: name, 124 }}, 125 }, 126 }, 127 WantErr: true, 128 }, { 129 Name: "secret and VWH exist, missing other stuff", 130 Key: key, 131 Objects: []runtime.Object{ 132 secret, ns, 133 &admissionregistrationv1.ValidatingWebhookConfiguration{ 134 ObjectMeta: metav1.ObjectMeta{ 135 Name: name, 136 }, 137 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 138 Name: name, 139 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 140 Service: &admissionregistrationv1.ServiceReference{ 141 Namespace: system.Namespace(), 142 Name: "webhook", 143 }, 144 }, 145 }}, 146 }, 147 }, 148 WantUpdates: []clientgotesting.UpdateActionImpl{{ 149 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 150 ObjectMeta: metav1.ObjectMeta{ 151 Name: name, 152 OwnerReferences: expectedOwnerReferences, 153 }, 154 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 155 Name: name, 156 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 157 Service: &admissionregistrationv1.ServiceReference{ 158 Namespace: system.Namespace(), 159 Name: "webhook", 160 // Path is added. 161 Path: ptr.String(path), 162 }, 163 // CABundle is added. 164 CABundle: []byte("present"), 165 }, 166 // Rules are added. 167 Rules: expectedRules, 168 }}, 169 }, 170 }}, 171 }, { 172 Name: "secret and VWH exist, added fields are incorrect", 173 Key: key, 174 Objects: []runtime.Object{ 175 secret, ns, 176 &admissionregistrationv1.ValidatingWebhookConfiguration{ 177 ObjectMeta: metav1.ObjectMeta{ 178 Name: name, 179 }, 180 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 181 Name: name, 182 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 183 Service: &admissionregistrationv1.ServiceReference{ 184 Namespace: system.Namespace(), 185 Name: "webhook", 186 // Incorrect 187 Path: ptr.String("incorrect"), 188 }, 189 // Incorrect 190 CABundle: []byte("incorrect"), 191 }, 192 // Incorrect 193 Rules: []admissionregistrationv1.RuleWithOperations{{ 194 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 195 Rule: admissionregistrationv1.Rule{ 196 APIGroups: []string{"pkg.knative.dev"}, 197 APIVersions: []string{"v1alpha1"}, 198 Resources: []string{"innerdefaultresources/*"}, 199 }, 200 }}, 201 }}, 202 }, 203 }, 204 WantUpdates: []clientgotesting.UpdateActionImpl{{ 205 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 206 ObjectMeta: metav1.ObjectMeta{ 207 Name: name, 208 OwnerReferences: expectedOwnerReferences, 209 }, 210 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 211 Name: name, 212 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 213 Service: &admissionregistrationv1.ServiceReference{ 214 Namespace: system.Namespace(), 215 Name: "webhook", 216 // Path is fixed. 217 Path: ptr.String(path), 218 }, 219 // CABundle is fixed. 220 CABundle: []byte("present"), 221 }, 222 // Rules are fixed. 223 Rules: expectedRules, 224 }}, 225 }, 226 }}, 227 }, { 228 Name: "failure updating VWH", 229 Key: key, 230 WantErr: true, 231 WithReactors: []clientgotesting.ReactionFunc{ 232 InduceFailure("update", "validatingwebhookconfigurations"), 233 }, 234 Objects: []runtime.Object{ 235 secret, ns, 236 &admissionregistrationv1.ValidatingWebhookConfiguration{ 237 ObjectMeta: metav1.ObjectMeta{ 238 Name: name, 239 }, 240 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 241 Name: name, 242 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 243 Service: &admissionregistrationv1.ServiceReference{ 244 Namespace: system.Namespace(), 245 Name: "webhook", 246 // Incorrect 247 Path: ptr.String("incorrect"), 248 }, 249 // Incorrect 250 CABundle: []byte("incorrect"), 251 }, 252 // Incorrect 253 Rules: []admissionregistrationv1.RuleWithOperations{{ 254 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 255 Rule: admissionregistrationv1.Rule{ 256 APIGroups: []string{"pkg.knative.dev"}, 257 APIVersions: []string{"v1alpha1"}, 258 Resources: []string{"innerdefaultresources/*"}, 259 }, 260 }}, 261 }}, 262 }, 263 }, 264 WantUpdates: []clientgotesting.UpdateActionImpl{{ 265 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 266 ObjectMeta: metav1.ObjectMeta{ 267 Name: name, 268 OwnerReferences: expectedOwnerReferences, 269 }, 270 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 271 Name: name, 272 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 273 Service: &admissionregistrationv1.ServiceReference{ 274 Namespace: system.Namespace(), 275 Name: "webhook", 276 // Path is fixed. 277 Path: ptr.String(path), 278 }, 279 // CABundle is fixed. 280 CABundle: []byte("present"), 281 }, 282 // Rules are fixed. 283 Rules: expectedRules, 284 }}, 285 }, 286 }}, 287 }, { 288 Name: ":fire: everything is fine :fire:", 289 Key: key, 290 Objects: []runtime.Object{ 291 secret, ns, 292 &admissionregistrationv1.ValidatingWebhookConfiguration{ 293 ObjectMeta: metav1.ObjectMeta{ 294 Name: name, 295 OwnerReferences: expectedOwnerReferences, 296 }, 297 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 298 Name: name, 299 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 300 Service: &admissionregistrationv1.ServiceReference{ 301 Namespace: system.Namespace(), 302 Name: "webhook", 303 // Path is fine. 304 Path: ptr.String(path), 305 }, 306 // CABundle is fine. 307 CABundle: []byte("present"), 308 }, 309 // Rules are fine. 310 Rules: expectedRules, 311 }}, 312 }, 313 }, 314 }} 315 316 table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { 317 wh := &reconciler{ 318 key: types.NamespacedName{ 319 Name: name, 320 }, 321 path: path, 322 323 client: kubeclient.Get(ctx), 324 vwhlister: listers.GetValidatingWebhookConfigurationLister(), 325 secretlister: listers.GetSecretLister(), 326 327 constructors: make(map[string]reflect.Value), 328 secretName: secretName, 329 } 330 331 for configName, constructor := range validations { 332 wh.registerConfig(configName, constructor) 333 } 334 335 return wh 336 })) 337 } 338 339 func TestNew(t *testing.T) { 340 ctx, _ := SetupFakeContext(t) 341 ctx = webhook.WithOptions(ctx, webhook.Options{}) 342 343 c := NewAdmissionController(ctx, "foo", "/bar", validations) 344 if c == nil { 345 t.Fatal("Expected NewController to return a non-nil value") 346 } 347 348 if want, got := 0, c.WorkQueue().Len(); want != got { 349 t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want) 350 } 351 352 la, ok := c.Reconciler.(pkgreconciler.LeaderAware) 353 if !ok { 354 t.Fatalf("%T is not leader aware", c.Reconciler) 355 } 356 357 if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil { 358 t.Error("Promote() =", err) 359 } 360 361 // Queue has async moving parts so if we check at the wrong moment, this might still be 0. 362 if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) { 363 return c.WorkQueue().Len() == 1, nil 364 }) != nil { 365 t.Error("Queue length was never 1") 366 } 367 }