Skip to content

Commit 761e005

Browse files
committed
Envtest should modify CRDs appropriately when using webhooks
Before this change, the webhook code was patching mutation or admission webhooks to point to the temporary URL and CA bundle generated when the webhook server would start. Conversion webhooks are handled at the CRD level in Kubernetes, and we need to make sure to patch those client configurations as well. This patch also unexports a lot of methods that should have been private from the very beginning, so it's marked as a breaking change. Signed-off-by: Vince Prignano <[email protected]>
1 parent ce2f0c9 commit 761e005

File tree

3 files changed

+75
-18
lines changed

3 files changed

+75
-18
lines changed

pkg/envtest/crd.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,16 @@ import (
2020
"bufio"
2121
"bytes"
2222
"context"
23+
"errors"
24+
"fmt"
2325
"io"
2426
"io/ioutil"
2527
"os"
2628
"path/filepath"
2729
"time"
2830

31+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
32+
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2933
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
3034
apierrors "k8s.io/apimachinery/pkg/api/errors"
3135
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -35,6 +39,7 @@ import (
3539
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
3640
"k8s.io/client-go/rest"
3741
"k8s.io/client-go/util/retry"
42+
"k8s.io/utils/pointer"
3843
"sigs.k8s.io/controller-runtime/pkg/client"
3944
"sigs.k8s.io/yaml"
4045
)
@@ -60,6 +65,13 @@ type CRDInstallOptions struct {
6065
// uninstalled when terminating the test environment.
6166
// Defaults to false.
6267
CleanUpAfterUse bool
68+
69+
// WebhookOptions contains the conversion webhook information to install
70+
// on the CRDs. This field is usually inherited by the EnvTest options.
71+
//
72+
// If you're passing this field manually, you need to make sure that
73+
// the CA information and host port is filled in properly.
74+
WebhookOptions WebhookInstallOptions
6375
}
6476

6577
const defaultPollInterval = 100 * time.Millisecond
@@ -74,6 +86,10 @@ func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]client.Objec
7486
return nil, err
7587
}
7688

89+
if err := modifyConversionWebhooks(options.CRDs, options.WebhookOptions); err != nil {
90+
return nil, err
91+
}
92+
7793
// Create the CRDs in the apiserver
7894
if err := CreateCRDs(config, options.CRDs); err != nil {
7995
return options.CRDs, err
@@ -340,6 +356,37 @@ func renderCRDs(options *CRDInstallOptions) ([]client.Object, error) {
340356
return res, nil
341357
}
342358

359+
func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstallOptions) error {
360+
if len(webhookOptions.LocalServingCAData) == 0 {
361+
return errors.New("cannot modify conversion webhooks, LocalServingCAData missing in WebhookOptions")
362+
}
363+
364+
// generate host port.
365+
hostPort, err := webhookOptions.generateHostPort()
366+
if err != nil {
367+
return err
368+
}
369+
url := pointer.StringPtr(fmt.Sprintf("https://%s/convert", hostPort))
370+
371+
for _, crd := range crds {
372+
switch c := crd.(type) {
373+
case *apiextensionsv1beta1.CustomResourceDefinition:
374+
c.Spec.Conversion.WebhookClientConfig.Service = nil
375+
c.Spec.Conversion.WebhookClientConfig = &apiextensionsv1beta1.WebhookClientConfig{
376+
CABundle: webhookOptions.LocalServingCAData,
377+
}
378+
case *apiextensionsv1.CustomResourceDefinition:
379+
c.Spec.Conversion.Webhook.ClientConfig.Service = nil
380+
c.Spec.Conversion.Webhook.ClientConfig = &apiextensionsv1.WebhookClientConfig{
381+
URL: url,
382+
CABundle: webhookOptions.LocalServingCAData,
383+
}
384+
}
385+
}
386+
387+
return nil
388+
}
389+
343390
// readCRDs reads the CRDs from files and Unmarshals them into structs
344391
func readCRDs(basePath string, files []os.FileInfo) ([]*unstructured.Unstructured, error) {
345392
var crds []*unstructured.Unstructured

pkg/envtest/server.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,12 @@ func (te *Environment) Start() (*rest.Config, error) {
263263
}
264264
}
265265

266+
// Call PrepWithoutInstalling to setup certificates first
267+
// and have them available to patch CRD conversion webhook as well.
268+
if err := te.WebhookInstallOptions.PrepWithoutInstalling(); err != nil {
269+
return nil, err
270+
}
271+
266272
log.V(1).Info("installing CRDs")
267273
te.CRDInstallOptions.CRDs = mergeCRDs(te.CRDInstallOptions.CRDs, te.CRDs)
268274
te.CRDInstallOptions.Paths = mergePaths(te.CRDInstallOptions.Paths, te.CRDDirectoryPaths)
@@ -274,9 +280,10 @@ func (te *Environment) Start() (*rest.Config, error) {
274280
te.CRDs = crds
275281

276282
log.V(1).Info("installing webhooks")
277-
err = te.WebhookInstallOptions.Install(te.Config)
278-
279-
return te.Config, err
283+
if err := te.WebhookInstallOptions.Install(te.Config); err != nil {
284+
return nil, err
285+
}
286+
return te.Config, nil
280287
}
281288

282289
func (te *Environment) startControlPlane() error {

pkg/envtest/webhook.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,10 @@ type WebhookInstallOptions struct {
8080
// ModifyWebhookDefinitions modifies webhook definitions by:
8181
// - applying CABundle based on the provided tinyca
8282
// - if webhook client config uses service spec, it's removed and replaced with direct url
83-
func (o *WebhookInstallOptions) ModifyWebhookDefinitions(caData []byte) error {
83+
func (o *WebhookInstallOptions) ModifyWebhookDefinitions() error {
84+
caData := o.LocalServingCAData
85+
86+
// generate host port.
8487
hostPort, err := o.generateHostPort()
8588
if err != nil {
8689
return err
@@ -161,16 +164,14 @@ func (o *WebhookInstallOptions) generateHostPort() (string, error) {
161164
// controller-runtime, where we need a random host-port & caData for webhook
162165
// tests, but may be useful in similar scenarios.
163166
func (o *WebhookInstallOptions) PrepWithoutInstalling() error {
164-
hookCA, err := o.setupCA()
165-
if err != nil {
167+
if err := o.setupCA(); err != nil {
166168
return err
167169
}
168170
if err := parseWebhook(o); err != nil {
169171
return err
170172
}
171173

172-
err = o.ModifyWebhookDefinitions(hookCA)
173-
if err != nil {
174+
if err := o.ModifyWebhookDefinitions(); err != nil {
174175
return err
175176
}
176177

@@ -179,8 +180,10 @@ func (o *WebhookInstallOptions) PrepWithoutInstalling() error {
179180

180181
// Install installs specified webhooks to the API server
181182
func (o *WebhookInstallOptions) Install(config *rest.Config) error {
182-
if err := o.PrepWithoutInstalling(); err != nil {
183-
return err
183+
if len(o.LocalServingCAData) == 0 {
184+
if err := o.PrepWithoutInstalling(); err != nil {
185+
return err
186+
}
184187
}
185188

186189
if err := createWebhooks(config, o.MutatingWebhooks, o.ValidatingWebhooks); err != nil {
@@ -269,38 +272,38 @@ func (p *webhookPoller) poll() (done bool, err error) {
269272
}
270273

271274
// setupCA creates CA for testing and writes them to disk
272-
func (o *WebhookInstallOptions) setupCA() ([]byte, error) {
275+
func (o *WebhookInstallOptions) setupCA() error {
273276
hookCA, err := integration.NewTinyCA()
274277
if err != nil {
275-
return nil, fmt.Errorf("unable to set up webhook CA: %v", err)
278+
return fmt.Errorf("unable to set up webhook CA: %v", err)
276279
}
277280

278281
names := []string{"localhost", o.LocalServingHost, o.LocalServingHostExternalName}
279282
hookCert, err := hookCA.NewServingCert(names...)
280283
if err != nil {
281-
return nil, fmt.Errorf("unable to set up webhook serving certs: %v", err)
284+
return fmt.Errorf("unable to set up webhook serving certs: %v", err)
282285
}
283286

284287
localServingCertsDir, err := ioutil.TempDir("", "envtest-serving-certs-")
285288
o.LocalServingCertDir = localServingCertsDir
286289
if err != nil {
287-
return nil, fmt.Errorf("unable to create directory for webhook serving certs: %v", err)
290+
return fmt.Errorf("unable to create directory for webhook serving certs: %v", err)
288291
}
289292

290293
certData, keyData, err := hookCert.AsBytes()
291294
if err != nil {
292-
return nil, fmt.Errorf("unable to marshal webhook serving certs: %v", err)
295+
return fmt.Errorf("unable to marshal webhook serving certs: %v", err)
293296
}
294297

295298
if err := ioutil.WriteFile(filepath.Join(localServingCertsDir, "tls.crt"), certData, 0640); err != nil {
296-
return nil, fmt.Errorf("unable to write webhook serving cert to disk: %v", err)
299+
return fmt.Errorf("unable to write webhook serving cert to disk: %v", err)
297300
}
298301
if err := ioutil.WriteFile(filepath.Join(localServingCertsDir, "tls.key"), keyData, 0640); err != nil {
299-
return nil, fmt.Errorf("unable to write webhook serving key to disk: %v", err)
302+
return fmt.Errorf("unable to write webhook serving key to disk: %v", err)
300303
}
301304

302305
o.LocalServingCAData = certData
303-
return certData, nil
306+
return err
304307
}
305308

306309
func createWebhooks(config *rest.Config, mutHooks []client.Object, valHooks []client.Object) error {

0 commit comments

Comments
 (0)