Skip to content

Commit e2d06be

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 e2d06be

File tree

3 files changed

+71
-20
lines changed

3 files changed

+71
-20
lines changed

pkg/envtest/crd.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@ import (
2020
"bufio"
2121
"bytes"
2222
"context"
23+
"fmt"
2324
"io"
2425
"io/ioutil"
2526
"os"
2627
"path/filepath"
2728
"time"
2829

30+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
31+
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
2932
"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
3033
apierrors "k8s.io/apimachinery/pkg/api/errors"
3134
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -35,6 +38,7 @@ import (
3538
k8syaml "k8s.io/apimachinery/pkg/util/yaml"
3639
"k8s.io/client-go/rest"
3740
"k8s.io/client-go/util/retry"
41+
"k8s.io/utils/pointer"
3842
"sigs.k8s.io/controller-runtime/pkg/client"
3943
"sigs.k8s.io/yaml"
4044
)
@@ -60,6 +64,13 @@ type CRDInstallOptions struct {
6064
// uninstalled when terminating the test environment.
6165
// Defaults to false.
6266
CleanUpAfterUse bool
67+
68+
// WebhookOptions contains the conversion webhook information to install
69+
// on the CRDs. This field is usually inherited by the EnvTest options.
70+
//
71+
// If you're passing this field manually, you need to make sure that
72+
// the CA information and host port is filled in properly.
73+
WebhookOptions WebhookInstallOptions
6374
}
6475

6576
const defaultPollInterval = 100 * time.Millisecond
@@ -74,6 +85,10 @@ func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]client.Objec
7485
return nil, err
7586
}
7687

88+
if err := modifyConversionWebhooks(options.CRDs, options.WebhookOptions); err != nil {
89+
return nil, err
90+
}
91+
7792
// Create the CRDs in the apiserver
7893
if err := CreateCRDs(config, options.CRDs); err != nil {
7994
return options.CRDs, err
@@ -340,6 +355,37 @@ func renderCRDs(options *CRDInstallOptions) ([]client.Object, error) {
340355
return res, nil
341356
}
342357

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

pkg/envtest/server.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -263,20 +263,28 @@ 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)
269275
te.CRDInstallOptions.ErrorIfPathMissing = te.ErrorIfCRDPathMissing
276+
te.CRDInstallOptions.WebhookOptions = te.WebhookInstallOptions
270277
crds, err := InstallCRDs(te.Config, te.CRDInstallOptions)
271278
if err != nil {
272279
return te.Config, err
273280
}
274281
te.CRDs = crds
275282

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

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

pkg/envtest/webhook.go

Lines changed: 14 additions & 17 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,10 +180,6 @@ 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
184-
}
185-
186183
if err := createWebhooks(config, o.MutatingWebhooks, o.ValidatingWebhooks); err != nil {
187184
return err
188185
}
@@ -269,38 +266,38 @@ func (p *webhookPoller) poll() (done bool, err error) {
269266
}
270267

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

278275
names := []string{"localhost", o.LocalServingHost, o.LocalServingHostExternalName}
279276
hookCert, err := hookCA.NewServingCert(names...)
280277
if err != nil {
281-
return nil, fmt.Errorf("unable to set up webhook serving certs: %v", err)
278+
return fmt.Errorf("unable to set up webhook serving certs: %v", err)
282279
}
283280

284281
localServingCertsDir, err := ioutil.TempDir("", "envtest-serving-certs-")
285282
o.LocalServingCertDir = localServingCertsDir
286283
if err != nil {
287-
return nil, fmt.Errorf("unable to create directory for webhook serving certs: %v", err)
284+
return fmt.Errorf("unable to create directory for webhook serving certs: %v", err)
288285
}
289286

290287
certData, keyData, err := hookCert.AsBytes()
291288
if err != nil {
292-
return nil, fmt.Errorf("unable to marshal webhook serving certs: %v", err)
289+
return fmt.Errorf("unable to marshal webhook serving certs: %v", err)
293290
}
294291

295292
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)
293+
return fmt.Errorf("unable to write webhook serving cert to disk: %v", err)
297294
}
298295
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)
296+
return fmt.Errorf("unable to write webhook serving key to disk: %v", err)
300297
}
301298

302299
o.LocalServingCAData = certData
303-
return certData, nil
300+
return err
304301
}
305302

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

0 commit comments

Comments
 (0)