Skip to content

Commit 0080db8

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 0080db8

File tree

4 files changed

+75
-36
lines changed

4 files changed

+75
-36
lines changed

pkg/envtest/crd.go

Lines changed: 36 additions & 1 deletion
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
)
@@ -66,14 +70,18 @@ const defaultPollInterval = 100 * time.Millisecond
6670
const defaultMaxWait = 10 * time.Second
6771

6872
// InstallCRDs installs a collection of CRDs into a cluster by reading the crd yaml files from a directory
69-
func InstallCRDs(config *rest.Config, options CRDInstallOptions) ([]client.Object, error) {
73+
func InstallCRDs(config *rest.Config, options CRDInstallOptions, webhookOptions WebhookInstallOptions) ([]client.Object, error) {
7074
defaultCRDOptions(&options)
7175

7276
// Read the CRD yamls into options.CRDs
7377
if err := readCRDFiles(&options); err != nil {
7478
return nil, err
7579
}
7680

81+
if err := modifyConversionWebhooks(options.CRDs, webhookOptions); err != nil {
82+
return nil, err
83+
}
84+
7785
// Create the CRDs in the apiserver
7886
if err := CreateCRDs(config, options.CRDs); err != nil {
7987
return options.CRDs, err
@@ -340,6 +348,33 @@ func renderCRDs(options *CRDInstallOptions) ([]client.Object, error) {
340348
return res, nil
341349
}
342350

351+
func modifyConversionWebhooks(crds []client.Object, webhookOptions WebhookInstallOptions) error {
352+
// generate host port.
353+
hostPort, err := webhookOptions.generateHostPort()
354+
if err != nil {
355+
return err
356+
}
357+
url := pointer.StringPtr(fmt.Sprintf("https://%s/convert", hostPort))
358+
359+
for _, crd := range crds {
360+
switch c := crd.(type) {
361+
case *apiextensionsv1beta1.CustomResourceDefinition:
362+
c.Spec.Conversion.WebhookClientConfig.Service = nil
363+
c.Spec.Conversion.WebhookClientConfig = &apiextensionsv1beta1.WebhookClientConfig{
364+
CABundle: webhookOptions.LocalServingCAData,
365+
}
366+
case *apiextensionsv1.CustomResourceDefinition:
367+
c.Spec.Conversion.Webhook.ClientConfig.Service = nil
368+
c.Spec.Conversion.Webhook.ClientConfig = &apiextensionsv1.WebhookClientConfig{
369+
URL: url,
370+
CABundle: webhookOptions.LocalServingCAData,
371+
}
372+
}
373+
}
374+
375+
return nil
376+
}
377+
343378
// readCRDs reads the CRDs from files and Unmarshals them into structs
344379
func readCRDs(basePath string, files []os.FileInfo) ([]*unstructured.Unstructured, error) {
345380
var crds []*unstructured.Unstructured

pkg/envtest/envtest_test.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ var _ = Describe("Test", func() {
8484
It("should install the unserved CRDs into the cluster", func() {
8585
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
8686
Paths: []string{filepath.Join(".", "testdata", "crds", "examplecrd_unserved.yaml")},
87-
})
87+
}, WebhookInstallOptions{})
8888
Expect(err).NotTo(HaveOccurred())
8989

9090
// Expect to find the CRDs
@@ -122,7 +122,7 @@ var _ = Describe("Test", func() {
122122
It("should install the CRDs into the cluster using directory", func(done Done) {
123123
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
124124
Paths: []string{validDirectory},
125-
})
125+
}, WebhookInstallOptions{})
126126
Expect(err).NotTo(HaveOccurred())
127127

128128
// Expect to find the CRDs
@@ -226,7 +226,7 @@ var _ = Describe("Test", func() {
226226
It("should install the CRDs into the cluster using file", func(done Done) {
227227
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
228228
Paths: []string{filepath.Join(".", "testdata", "crds", "examplecrd3.yaml")},
229-
})
229+
}, WebhookInstallOptions{})
230230
Expect(err).NotTo(HaveOccurred())
231231

232232
crd := &v1beta1.CustomResourceDefinition{}
@@ -257,7 +257,7 @@ var _ = Describe("Test", func() {
257257
filepath.Join(".", "testdata", "examplecrd.yaml"),
258258
filepath.Join(".", "testdata", "examplecrd_v1.yaml"),
259259
},
260-
})
260+
}, WebhookInstallOptions{})
261261
Expect(err).NotTo(HaveOccurred())
262262
Expect(crds).To(HaveLen(2))
263263

@@ -270,7 +270,7 @@ var _ = Describe("Test", func() {
270270
filepath.Join(".", "testdata"),
271271
filepath.Join(".", "testdata", "examplecrd1.yaml"),
272272
},
273-
})
273+
}, WebhookInstallOptions{})
274274
Expect(err).NotTo(HaveOccurred())
275275

276276
crd := &apiextensionsv1.CustomResourceDefinition{}
@@ -307,7 +307,7 @@ var _ = Describe("Test", func() {
307307
}, 10)
308308

309309
It("should not return an not error if the directory doesn't exist", func(done Done) {
310-
crds, err = InstallCRDs(env.Config, CRDInstallOptions{Paths: []string{invalidDirectory}})
310+
crds, err = InstallCRDs(env.Config, CRDInstallOptions{Paths: []string{invalidDirectory}}, WebhookInstallOptions{})
311311
Expect(err).NotTo(HaveOccurred())
312312

313313
close(done)
@@ -316,7 +316,7 @@ var _ = Describe("Test", func() {
316316
It("should return an error if the directory doesn't exist", func(done Done) {
317317
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
318318
Paths: []string{invalidDirectory}, ErrorIfPathMissing: true,
319-
})
319+
}, WebhookInstallOptions{})
320320
Expect(err).To(HaveOccurred())
321321

322322
close(done)
@@ -325,7 +325,7 @@ var _ = Describe("Test", func() {
325325
It("should return an error if the file doesn't exist", func(done Done) {
326326
crds, err = InstallCRDs(env.Config, CRDInstallOptions{Paths: []string{
327327
filepath.Join(".", "testdata", "fake.yaml")}, ErrorIfPathMissing: true,
328-
})
328+
}, WebhookInstallOptions{})
329329
Expect(err).To(HaveOccurred())
330330

331331
close(done)
@@ -353,7 +353,7 @@ var _ = Describe("Test", func() {
353353
It("should return an error if the resource isn't found in the group version", func(done Done) {
354354
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
355355
Paths: []string{"."},
356-
})
356+
}, WebhookInstallOptions{})
357357
Expect(err).NotTo(HaveOccurred())
358358

359359
// Wait for a CRD that doesn't exist, but the Group and Version do
@@ -385,7 +385,7 @@ var _ = Describe("Test", func() {
385385

386386
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
387387
Paths: []string{filepath.Join(".", "testdata")},
388-
})
388+
}, WebhookInstallOptions{})
389389
Expect(err).NotTo(HaveOccurred())
390390

391391
// Expect to find the CRDs
@@ -487,7 +487,7 @@ var _ = Describe("Test", func() {
487487

488488
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
489489
Paths: []string{filepath.Join(".", "testdata")},
490-
})
490+
}, WebhookInstallOptions{})
491491
Expect(err).NotTo(HaveOccurred())
492492

493493
// Expect to find the CRDs
@@ -594,7 +594,7 @@ var _ = Describe("Test", func() {
594594
// Install only the CRDv1 multi-version example
595595
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
596596
Paths: []string{filepath.Join(".", "testdata")},
597-
})
597+
}, WebhookInstallOptions{})
598598
Expect(err).NotTo(HaveOccurred())
599599

600600
// Expect to find the CRDs
@@ -636,7 +636,7 @@ var _ = Describe("Test", func() {
636636
// Add one more version and update
637637
_, err = InstallCRDs(env.Config, CRDInstallOptions{
638638
Paths: []string{filepath.Join(".", "testdata", "crdv1_updated")},
639-
})
639+
}, WebhookInstallOptions{})
640640
Expect(err).NotTo(HaveOccurred())
641641

642642
// Expect to find updated CRD
@@ -686,7 +686,7 @@ var _ = Describe("Test", func() {
686686

687687
crds, err = InstallCRDs(env.Config, CRDInstallOptions{
688688
Paths: []string{validDirectory},
689-
})
689+
}, WebhookInstallOptions{})
690690
Expect(err).NotTo(HaveOccurred())
691691

692692
// Expect to find the CRDs

pkg/envtest/server.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,20 +263,27 @@ 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
270-
crds, err := InstallCRDs(te.Config, te.CRDInstallOptions)
276+
crds, err := InstallCRDs(te.Config, te.CRDInstallOptions, te.WebhookInstallOptions)
271277
if err != nil {
272278
return te.Config, err
273279
}
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: 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)