• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

NVIDIA / skyhook / 20480843614

24 Dec 2025 07:19AM UTC coverage: 81.591% (+6.2%) from 75.355%
20480843614

Pull #142

github

web-flow
Merge fe4d056de into 96d3daeb5
Pull Request #142: feat: add webhook support for validation policies exist

110 of 130 new or added lines in 6 files covered. (84.62%)

57 existing lines in 1 file now uncovered.

6391 of 7833 relevant lines covered (81.59%)

4.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

60.47
/operator/internal/controller/webhook_controller.go
1
/*
2
 * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
3
 * SPDX-License-Identifier: Apache-2.0
4
 *
5
 *
6
 * Licensed under the Apache License, Version 2.0 (the "License");
7
 * you may not use this file except in compliance with the License.
8
 * You may obtain a copy of the License at
9
 *
10
 * http://www.apache.org/licenses/LICENSE-2.0
11
 *
12
 * Unless required by applicable law or agreed to in writing, software
13
 * distributed under the License is distributed on an "AS IS" BASIS,
14
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
 * See the License for the specific language governing permissions and
16
 * limitations under the License.
17
 */
18

19
package controller
20

21
import (
22
        "bytes"
23
        "context"
24
        "fmt"
25
        "net/http"
26
        "reflect"
27
        "time"
28

29
        "sigs.k8s.io/controller-runtime/pkg/builder"
30
        "sigs.k8s.io/controller-runtime/pkg/predicate"
31

32
        "github.com/NVIDIA/skyhook/operator/api/v1alpha1"
33
        admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
34
        corev1 "k8s.io/api/core/v1"
35
        "k8s.io/apimachinery/pkg/api/errors"
36
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37
        "k8s.io/apimachinery/pkg/types"
38
        ctrl "sigs.k8s.io/controller-runtime"
39
        runtimecache "sigs.k8s.io/controller-runtime/pkg/cache"
40
        "sigs.k8s.io/controller-runtime/pkg/client"
41
        "sigs.k8s.io/controller-runtime/pkg/log"
42
        "sigs.k8s.io/controller-runtime/pkg/reconcile"
43
)
44

45
// This project used to use cert-manager to generate the webhook certificates.
46
// This removes the dependency on cert-manager and simplifies the deployment.
47
// This also removes the need to have a specific issuer, and just uses a self-signed cert.
48
type WebhookControllerOptions struct { // prefix these with WEBHOOK_
49
        SecretName  string `env:"WEBHOOK_SECRET_NAME, default=webhook-cert"`
50
        ServiceName string `env:"WEBHOOK_SERVICE_NAME, default=skyhook-operator-webhook-service"`
51
}
52

53
type WebhookController struct {
54
        client.Client
55
        cache     runtimecache.Cache
56
        namespace string
57
        certDir   string
58
        opts      WebhookControllerOptions
59
}
60

61
func NewWebhookController(client client.Client, cache runtimecache.Cache, namespace, certDir string, opts WebhookControllerOptions) (*WebhookController, error) {
×
62
        if err := ensureDummyCert(certDir); err != nil {
×
63
                return nil, err
×
64
        }
×
65

66
        return &WebhookController{
×
67
                Client:    client,
×
UNCOV
68
                cache:     cache,
×
UNCOV
69
                namespace: namespace,
×
UNCOV
70
                certDir:   certDir,
×
UNCOV
71
                opts:      opts,
×
UNCOV
72
        }, nil
×
73
}
74

75
// Start implements the Runnable interface to ensure certificates are set up before the webhook server starts
UNCOV
76
func (r *WebhookController) Start(ctx context.Context) error {
×
UNCOV
77
        logger := log.FromContext(ctx)
×
UNCOV
78
        logger.Info("Setting up webhook certificates")
×
UNCOV
79

×
UNCOV
80
        // wait for the cache to sync
×
UNCOV
81
        if cache := r.cache.WaitForCacheSync(ctx); !cache {
×
UNCOV
82
                return fmt.Errorf("failed to wait for cache to sync")
×
UNCOV
83
        }
×
84
        // starts the reconcile process off
85
        _, err := r.GetOrCreateWebhookCertSecret(ctx, r.opts.SecretName, r.namespace)
×
86
        if err != nil {
×
87
                if errors.IsAlreadyExists(err) {
×
UNCOV
88
                        return nil // ignore this special case, it just needs to exist
×
89
                }
×
90
                return err
×
91
        }
92

93
        logger.Info("Webhook certificates setup complete")
×
94
        return nil
×
95
}
96

97
// NeedLeaderElection implements the Runnable interface, runs only on leader
UNCOV
98
func (r *WebhookController) NeedLeaderElection() bool {
×
99
        return true
×
100
}
×
101

102
func (r *WebhookController) SetupWithManager(mgr ctrl.Manager) error {
×
103
        return ctrl.NewControllerManagedBy(mgr).
×
104
                For(&corev1.Secret{}, builder.WithPredicates(predicate.NewPredicateFuncs(func(obj client.Object) bool {
×
105
                        return obj.GetNamespace() == r.namespace && obj.GetName() == r.opts.SecretName
×
106
                }))).
×
107
                Complete(r)
108
}
109

110
// permissions
111
//+kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations;mutatingwebhookconfigurations,verbs=get;list;watch;create;update;patch;delete
112
//+kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete
113

114
// Reconcile is the main function that reconciles the webhook controller
UNCOV
115
func (r *WebhookController) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
×
116
        logger := log.FromContext(ctx)
×
117
        logger.Info("Reconciling webhook controller")
×
UNCOV
118

×
UNCOV
119
        // if its deleted, skip reconciliation, this is for cleanup
×
UNCOV
120
        obj := &corev1.Secret{}
×
121
        if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
×
122
                // handle not found, etc.
×
123
                return ctrl.Result{}, client.IgnoreNotFound(err)
×
UNCOV
124
        }
×
125

126
        // If the object is being deleted, skip reconciliation
127
        if !obj.ObjectMeta.DeletionTimestamp.IsZero() {
×
128
                // Optionally: handle finalizers here if you want
×
129
                return ctrl.Result{}, nil
×
UNCOV
130
        }
×
131

132
        // 1. Get or create/update the Secret with certs
133
        // 2. Get or create/update the webhook configurations
134

135
        // Example: check if secret exists
UNCOV
136
        secret, err := r.GetOrCreateWebhookCertSecret(ctx, r.opts.SecretName, r.namespace)
×
UNCOV
137
        if err != nil {
×
138
                return reconcile.Result{}, err
×
139
        }
×
140

141
        _, err = r.CheckOrUpdateWebhookCertSecret(ctx, secret)
×
142
        if err != nil {
×
143
                return reconcile.Result{}, err
×
144
        }
×
145

146
        _, err = r.CheckOrUpdateWebhookConfigurations(ctx, secret)
×
147
        if err != nil {
×
UNCOV
148
                return reconcile.Result{}, err
×
UNCOV
149
        }
×
150

151
        logger.Info("Reconciled webhook controller")
×
152
        return reconcile.Result{RequeueAfter: 24 * time.Hour}, nil // requeue for periodic rotation/check
×
153
}
154

155
// GetOrCreateWebhookCertSecret returns a new secret with the given name and the given CA and cert.
156
func (r *WebhookController) GetOrCreateWebhookCertSecret(ctx context.Context, secretName, namespace string) (*corev1.Secret, error) {
1✔
157

1✔
158
        // get the secret
1✔
159
        secret := &corev1.Secret{}
1✔
160
        err := r.Get(ctx, types.NamespacedName{Name: secretName, Namespace: namespace}, secret)
1✔
161
        if err != nil {
2✔
162
                if errors.IsNotFound(err) {
2✔
163
                        // not found, create it
1✔
164
                        webhookCert, err := generateCert(r.opts.ServiceName, r.namespace, 365*24*time.Hour) // TODO: this should be configured
1✔
165
                        if err != nil {
1✔
166
                                return nil, err
×
167
                        }
×
168

169
                        // Write cert and key to disk if CertDir is set
170
                        if r.certDir != "" {
2✔
171
                                _ = writeCertAndKey([]byte(webhookCert.TLSCert), []byte(webhookCert.TLSKey), r.certDir)
1✔
172
                        }
1✔
173

174
                        secret = webhookCert.ToSecret(secretName, namespace, r.opts.ServiceName)
1✔
175

1✔
176
                        if err := r.Create(ctx, secret); err != nil {
1✔
UNCOV
177
                                return nil, err
×
UNCOV
178
                        }
×
179

180
                        return secret, nil
1✔
181
                }
UNCOV
182
                return nil, err
×
183
        }
184

185
        // found, return it
UNCOV
186
        return secret, nil
×
187
}
188

189
func (r *WebhookController) CheckOrUpdateWebhookCertSecret(ctx context.Context, secret *corev1.Secret) (bool, error) {
1✔
190
        equal, err := compareCertOnDiskToSecret(r.certDir, secret)
1✔
191
        if err != nil {
1✔
UNCOV
192
                return false, err
×
UNCOV
193
        }
×
194

195
        // check if the secret is going to expire in the next 168 hours or if the cert on disk is different from the secret
196
        if !equal || secret.Annotations[fmt.Sprintf("%s/expiration", v1alpha1.METADATA_PREFIX)] < time.Now().Add(168*time.Hour).Format(time.RFC3339) {
2✔
197
                // expired, generate a new cert
1✔
198
                webhookCert, err := generateCert(r.opts.ServiceName, r.namespace, 365*24*time.Hour) // TODO: this should be configured
1✔
199
                if err != nil {
1✔
200
                        return false, err
×
201
                }
×
202

203
                // Write cert and key to disk if CertDir is set
204
                if r.certDir != "" {
2✔
205
                        _ = writeCertAndKey([]byte(webhookCert.TLSCert), []byte(webhookCert.TLSKey), r.certDir)
1✔
206
                }
1✔
207

208
                secret.Data["ca.crt"] = webhookCert.CABytes
1✔
209
                secret.Data["tls.crt"] = []byte(webhookCert.TLSCert)
1✔
210
                secret.Data["tls.key"] = []byte(webhookCert.TLSKey)
1✔
211
                secret.Annotations[fmt.Sprintf("%s/expiration", v1alpha1.METADATA_PREFIX)] = webhookCert.Expiration.Format(time.RFC3339)
1✔
212

1✔
213
                return true, r.Update(ctx, secret)
1✔
214
        }
215

UNCOV
216
        return false, nil
×
217
}
218

UNCOV
219
func (r *WebhookController) CheckOrUpdateWebhookConfigurations(ctx context.Context, secret *corev1.Secret) (bool, error) {
×
220
        // Update only CABundle fields of existing webhook configurations created by Helm
×
221
        caBundle := secret.Data["ca.crt"]
×
UNCOV
222
        changed := false
×
223

×
UNCOV
224
        // ValidatingWebhookConfiguration
×
225
        validatingName := webhookValidatingWebhookConfiguration(r.namespace, r.opts.ServiceName, secret).GetName()
×
226
        existingValidating := &admissionregistrationv1.ValidatingWebhookConfiguration{}
×
UNCOV
227
        if err := r.Get(ctx, types.NamespacedName{Name: validatingName}, existingValidating); err != nil {
×
UNCOV
228
                if errors.IsNotFound(err) {
×
UNCOV
229
                        return false, fmt.Errorf("ValidatingWebhookConfiguration %q not found; creation is handled by the Helm chart. Ensure the chart is installed and webhooks are enabled: %w", validatingName, err)
×
UNCOV
230
                }
×
UNCOV
231
                return false, fmt.Errorf("failed to get ValidatingWebhookConfiguration %q: %w", validatingName, err)
×
232
        }
233

UNCOV
234
        needUpdate := false
×
NEW
UNCOV
235
        expectedRules := validatingWebhookRules()
×
236
        for i := range existingValidating.Webhooks {
×
NEW
UNCOV
237
                if validatingWebhookNeedsUpdate(&existingValidating.Webhooks[i], caBundle, expectedRules) {
×
UNCOV
238
                        needUpdate = true
×
UNCOV
239
                }
×
240
        }
241
        if needUpdate {
×
UNCOV
242
                if err := r.Update(ctx, existingValidating); err != nil {
×
UNCOV
243
                        return false, err
×
244
                } else {
×
245
                        changed = true
×
246
                }
×
247
        }
248

249
        // MutatingWebhookConfiguration
250
        mutatingName := webhookMutatingWebhookConfiguration(r.namespace, r.opts.ServiceName, secret).GetName()
×
251
        existingMutating := &admissionregistrationv1.MutatingWebhookConfiguration{}
×
252
        if err := r.Get(ctx, types.NamespacedName{Name: mutatingName}, existingMutating); err != nil {
×
253
                if errors.IsNotFound(err) {
×
254
                        return changed, fmt.Errorf("MutatingWebhookConfiguration %q not found; creation is handled by the Helm chart. Ensure the chart is installed and webhooks are enabled: %w", mutatingName, err)
×
255
                }
×
256
                return false, fmt.Errorf("failed to get MutatingWebhookConfiguration %q: %w", mutatingName, err)
×
257
        }
258

259
        needUpdate = false
×
NEW
260
        mutatingRules := mutatingWebhookRules()
×
261
        for i := range existingMutating.Webhooks {
×
NEW
262
                if mutatingWebhookNeedsUpdate(&existingMutating.Webhooks[i], caBundle, mutatingRules) {
×
263
                        needUpdate = true
×
264
                }
×
265
        }
266
        if needUpdate {
×
267
                if err := r.Update(ctx, existingMutating); err != nil {
×
268
                        return false, err
×
269
                } else {
×
UNCOV
270
                        changed = true
×
UNCOV
271
                }
×
272
        }
273

274
        return changed, nil
×
275
}
276

277
// webhookValidatingWebhookConfiguration returns a new validating webhook configuration.
278
func webhookValidatingWebhookConfiguration(namespace, serviceName string, secret *corev1.Secret) *admissionregistrationv1.ValidatingWebhookConfiguration {
1✔
279
        conf := admissionregistrationv1.ValidatingWebhookConfiguration{
1✔
280
                ObjectMeta: metav1.ObjectMeta{
1✔
281
                        Name: "skyhook-operator-validating-webhook",
1✔
282
                },
1✔
283
                Webhooks: []admissionregistrationv1.ValidatingWebhook{
1✔
284
                        {
1✔
285
                                Name:                    "validate-skyhook.nvidia.com",
1✔
286
                                ClientConfig:            webhookClient(serviceName, namespace, "/validate-skyhook-nvidia-com-v1alpha1-skyhook", secret),
1✔
287
                                FailurePolicy:           ptr(admissionregistrationv1.Fail),
1✔
288
                                Rules:                   validatingWebhookRules(),
1✔
289
                                SideEffects:             ptr(admissionregistrationv1.SideEffectClassNone),
1✔
290
                                AdmissionReviewVersions: []string{"v1"},
1✔
291
                        },
1✔
292
                        {
1✔
293
                                Name:                    "validate-deploymentpolicy.nvidia.com",
1✔
294
                                ClientConfig:            webhookClient(serviceName, namespace, "/validate-skyhook-nvidia-com-v1alpha1-deploymentpolicy", secret),
1✔
295
                                FailurePolicy:           ptr(admissionregistrationv1.Fail),
1✔
296
                                Rules:                   validatingWebhookRules(),
1✔
297
                                SideEffects:             ptr(admissionregistrationv1.SideEffectClassNone),
1✔
298
                                AdmissionReviewVersions: []string{"v1"},
1✔
299
                        },
1✔
300
                },
1✔
301
        }
1✔
302

1✔
303
        return &conf
1✔
304
}
1✔
305

306
// webhookMutatingWebhookConfiguration returns a new mutating webhook configuration.
307
func webhookMutatingWebhookConfiguration(namespace, serviceName string, secret *corev1.Secret) *admissionregistrationv1.MutatingWebhookConfiguration {
1✔
308
        conf := admissionregistrationv1.MutatingWebhookConfiguration{
1✔
309
                ObjectMeta: metav1.ObjectMeta{
1✔
310
                        Name: "skyhook-operator-mutating-webhook",
1✔
311
                },
1✔
312
                Webhooks: []admissionregistrationv1.MutatingWebhook{
1✔
313
                        {
1✔
314
                                Name:                    "mutate-skyhook.nvidia.com",
1✔
315
                                ClientConfig:            webhookClient(serviceName, namespace, "/mutate-skyhook-nvidia-com-v1alpha1-skyhook", secret),
1✔
316
                                FailurePolicy:           ptr(admissionregistrationv1.Fail),
1✔
317
                                Rules:                   mutatingWebhookRules(),
1✔
318
                                SideEffects:             ptr(admissionregistrationv1.SideEffectClassNone),
1✔
319
                                AdmissionReviewVersions: []string{"v1"},
1✔
320
                        },
1✔
321
                        {
1✔
322
                                Name:                    "mutate-deploymentpolicy.nvidia.com",
1✔
323
                                ClientConfig:            webhookClient(serviceName, namespace, "/mutate-skyhook-nvidia-com-v1alpha1-deploymentpolicy", secret),
1✔
324
                                FailurePolicy:           ptr(admissionregistrationv1.Fail),
1✔
325
                                Rules:                   mutatingWebhookRules(),
1✔
326
                                SideEffects:             ptr(admissionregistrationv1.SideEffectClassNone),
1✔
327
                                AdmissionReviewVersions: []string{"v1"},
1✔
328
                        },
1✔
329
                },
1✔
330
        }
1✔
331

1✔
332
        return &conf
1✔
333
}
1✔
334

335
func compareMutatingWebhookConfigurations(a, b *admissionregistrationv1.MutatingWebhookConfiguration) bool {
1✔
336
        if len(a.Webhooks) != len(b.Webhooks) {
2✔
337
                return true
1✔
338
        }
1✔
339
        for i := range a.Webhooks {
2✔
340
                if !bytes.Equal(a.Webhooks[i].ClientConfig.CABundle, b.Webhooks[i].ClientConfig.CABundle) {
2✔
341
                        return true
1✔
342
                }
1✔
343
        }
344
        return false
1✔
345
}
346

347
func compareValidatingWebhookConfigurations(a, b *admissionregistrationv1.ValidatingWebhookConfiguration) bool {
1✔
348
        if len(a.Webhooks) != len(b.Webhooks) {
2✔
349
                return true
1✔
350
        }
1✔
351
        for i := range a.Webhooks {
2✔
352
                if !bytes.Equal(a.Webhooks[i].ClientConfig.CABundle, b.Webhooks[i].ClientConfig.CABundle) {
2✔
353
                        return true
1✔
354
                }
1✔
355
        }
356
        return false
1✔
357
}
358

359
func webhookClient(serviceName, namespace, path string, secret *corev1.Secret) admissionregistrationv1.WebhookClientConfig {
1✔
360
        return admissionregistrationv1.WebhookClientConfig{
1✔
361
                Service: &admissionregistrationv1.ServiceReference{
1✔
362
                        Name:      serviceName,
1✔
363
                        Namespace: namespace,
1✔
364
                        Path:      ptr(path),
1✔
365
                },
1✔
366
                CABundle: secret.Data["ca.crt"],
1✔
367
        }
1✔
368
}
1✔
369

370
func validatingWebhookRules() []admissionregistrationv1.RuleWithOperations {
1✔
371
        return []admissionregistrationv1.RuleWithOperations{
1✔
372
                {
1✔
373
                        Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
1✔
374
                        Rule: admissionregistrationv1.Rule{
1✔
375
                                APIGroups:   []string{v1alpha1.GroupVersion.Group},
1✔
376
                                APIVersions: []string{v1alpha1.GroupVersion.Version},
1✔
377
                                Resources:   []string{"skyhooks"},
1✔
378
                        },
1✔
379
                },
1✔
380
                {
1✔
381
                        Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete},
1✔
382
                        Rule: admissionregistrationv1.Rule{
1✔
383
                                APIGroups:   []string{v1alpha1.GroupVersion.Group},
1✔
384
                                APIVersions: []string{v1alpha1.GroupVersion.Version},
1✔
385
                                Resources:   []string{"deploymentpolicies"},
1✔
386
                        },
1✔
387
                },
1✔
388
        }
1✔
389
}
1✔
390

391
func mutatingWebhookRules() []admissionregistrationv1.RuleWithOperations {
1✔
392
        return []admissionregistrationv1.RuleWithOperations{
1✔
393
                {
1✔
394
                        Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
1✔
395
                        Rule: admissionregistrationv1.Rule{
1✔
396
                                APIGroups:   []string{v1alpha1.GroupVersion.Group},
1✔
397
                                APIVersions: []string{v1alpha1.GroupVersion.Version},
1✔
398
                                Resources:   []string{"skyhooks"},
1✔
399
                        },
1✔
400
                },
1✔
401
                {
1✔
402
                        Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
1✔
403
                        Rule: admissionregistrationv1.Rule{
1✔
404
                                APIGroups:   []string{v1alpha1.GroupVersion.Group},
1✔
405
                                APIVersions: []string{v1alpha1.GroupVersion.Version},
1✔
406
                                Resources:   []string{"deploymentpolicies"},
1✔
407
                        },
1✔
408
                },
1✔
409
        }
1✔
410
}
1✔
411

412
// validatingWebhookNeedsUpdate checks if a validating webhook needs to be updated with new CABundle or Rules
413
// Returns true if updates were made to the webhook
414
func validatingWebhookNeedsUpdate(webhook *admissionregistrationv1.ValidatingWebhook, caBundle []byte, expectedRules []admissionregistrationv1.RuleWithOperations) bool {
1✔
415
        needUpdate := false
1✔
416

1✔
417
        // Check if CABundle needs to be set
1✔
418
        if len(webhook.ClientConfig.CABundle) == 0 {
1✔
NEW
UNCOV
419
                webhook.ClientConfig.CABundle = caBundle
×
NEW
UNCOV
420
                needUpdate = true
×
NEW
UNCOV
421
        }
×
422

423
        // Check if rules need to be updated
424
        if !reflect.DeepEqual(webhook.Rules, expectedRules) {
2✔
425
                webhook.Rules = expectedRules
1✔
426
                needUpdate = true
1✔
427
        }
1✔
428

429
        return needUpdate
1✔
430
}
431

432
// mutatingWebhookNeedsUpdate checks if a mutating webhook needs to be updated
433
func mutatingWebhookNeedsUpdate(webhook *admissionregistrationv1.MutatingWebhook, caBundle []byte, expectedRules []admissionregistrationv1.RuleWithOperations) bool {
1✔
434
        needUpdate := false
1✔
435

1✔
436
        // Check if CABundle needs to be set
1✔
437
        if len(webhook.ClientConfig.CABundle) == 0 {
2✔
438
                webhook.ClientConfig.CABundle = caBundle
1✔
439
                needUpdate = true
1✔
440
        }
1✔
441

442
        // Check if rules need to be updated
443
        if !reflect.DeepEqual(webhook.Rules, expectedRules) {
1✔
NEW
UNCOV
444
                webhook.Rules = expectedRules
×
NEW
UNCOV
445
                needUpdate = true
×
NEW
UNCOV
446
        }
×
447

448
        return needUpdate
1✔
449
}
450

451
// WebhookSecretReadyzCheck is a readyz check for the webhook secret, if it does not exist, it will return an error
452
// if it exists, it will wait for the secret to be ready, this makes sure that we don't start the operator
453
// if the webhook secret is not ready
454
func (r *WebhookController) WebhookSecretReadyzCheck(_ *http.Request) error {
1✔
455
        secret := &corev1.Secret{}
1✔
456
        err := r.Client.Get(context.Background(), types.NamespacedName{
1✔
457
                Name:      r.opts.SecretName,
1✔
458
                Namespace: r.namespace,
1✔
459
        }, secret)
1✔
460

1✔
461
        if err != nil {
2✔
462
                return err
1✔
463
        }
1✔
464

465
        equal, err := compareCertOnDiskToSecret(r.certDir, secret)
1✔
466
        if err != nil {
1✔
UNCOV
467
                return err
×
UNCOV
468
        }
×
469

470
        if !equal {
2✔
471
                return fmt.Errorf("webhook secret is not ready")
1✔
472
        }
1✔
473

474
        // check for the webhook configurations
475
        validatingWebhookName := webhookValidatingWebhookConfiguration(r.namespace, r.opts.ServiceName, secret).GetName()
1✔
476
        validatingWebhookConfiguration := &admissionregistrationv1.ValidatingWebhookConfiguration{}
1✔
477
        err = r.Get(context.Background(), types.NamespacedName{Name: validatingWebhookName}, validatingWebhookConfiguration)
1✔
478
        if err != nil {
2✔
479
                if errors.IsNotFound(err) {
2✔
480
                        return fmt.Errorf("ValidatingWebhookConfiguration %q not found. Either disable webhooks (not recommended) or reinstall the operator via the Helm chart to provision webhooks", validatingWebhookName)
1✔
481
                }
1✔
482
                return err
×
483
        }
484

485
        if !bytes.Equal(validatingWebhookConfiguration.Webhooks[0].ClientConfig.CABundle, secret.Data["ca.crt"]) {
2✔
486
                return fmt.Errorf("webhook secret is not ready, ca bundle is not equal to the validating webhook configuration")
1✔
487
        }
1✔
488

489
        mutatingWebhookConfiguration := webhookMutatingWebhookConfiguration(r.namespace, r.opts.ServiceName, secret)
1✔
490
        err = r.Get(context.Background(), types.NamespacedName{Name: mutatingWebhookConfiguration.Name}, mutatingWebhookConfiguration)
1✔
491
        if err != nil {
1✔
492
                if errors.IsNotFound(err) {
×
493
                        return fmt.Errorf("MutatingWebhookConfiguration %q not found. Either disable webhooks (not recommended) or reinstall the operator via the Helm chart to provision webhooks", mutatingWebhookConfiguration.Name)
×
494
                }
×
495
                return err
×
496
        }
497

498
        if !bytes.Equal(mutatingWebhookConfiguration.Webhooks[0].ClientConfig.CABundle, secret.Data["ca.crt"]) {
1✔
499
                return fmt.Errorf("webhook secret is not ready, ca bundle is not equal to the mutating webhook configuration")
×
500
        }
×
501

502
        return nil
1✔
503
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc