Skip to content

Commit 901b6f8

Browse files
author
Mengqi Yu
committed
add tests for simplified webhook interface
1 parent 8810470 commit 901b6f8

File tree

5 files changed

+323
-15
lines changed

5 files changed

+323
-15
lines changed

pkg/builder/build.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"strings"
2222

2323
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/runtime/schema"
2425
"k8s.io/client-go/rest"
2526
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
2627
"sigs.k8s.io/controller-runtime/pkg/client/config"
@@ -249,9 +250,6 @@ func (blder *Builder) doWebhook() error {
249250
return err
250251
}
251252

252-
partialPath := strings.Replace(gvk.Group, ".", "-", -1) + "-" +
253-
gvk.Version + "-" + strings.ToLower(gvk.Kind)
254-
255253
// TODO: When the conversion webhook lands, we need to handle all registered versions of a given group-kind.
256254
// A potential workflow for defaulting webhook
257255
// 1) a bespoke (non-hub) version comes in
@@ -267,7 +265,7 @@ func (blder *Builder) doWebhook() error {
267265
if defaulter, isDefaulter := blder.apiType.(admission.Defaulter); isDefaulter {
268266
mwh := admission.DefaultingWebhookFor(defaulter)
269267
if mwh != nil {
270-
path := "/mutate-" + partialPath
268+
path := generateMutatePath(gvk)
271269
log.Info("Registering a mutating webhook",
272270
"GVK", gvk,
273271
"path", path)
@@ -279,7 +277,7 @@ func (blder *Builder) doWebhook() error {
279277
if validator, isValidator := blder.apiType.(admission.Validator); isValidator {
280278
vwh := admission.ValidatingWebhookFor(validator)
281279
if vwh != nil {
282-
path := "/validate-" + partialPath
280+
path := generateValidatePath(gvk)
283281
log.Info("Registering a validating webhook",
284282
"GVK", gvk,
285283
"path", path)
@@ -289,3 +287,13 @@ func (blder *Builder) doWebhook() error {
289287

290288
return err
291289
}
290+
291+
func generateMutatePath(gvk schema.GroupVersionKind) string {
292+
return "/mutate-" + strings.Replace(gvk.Group, ".", "-", -1) + "-" +
293+
gvk.Version + "-" + strings.ToLower(gvk.Kind)
294+
}
295+
296+
func generateValidatePath(gvk schema.GroupVersionKind) string {
297+
return "/validate-" + strings.Replace(gvk.Group, ".", "-", -1) + "-" +
298+
gvk.Version + "-" + strings.ToLower(gvk.Kind)
299+
}

pkg/builder/build_test.go

Lines changed: 152 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,6 @@ import (
2121
"fmt"
2222
"strings"
2323

24-
"sigs.k8s.io/controller-runtime/pkg/handler"
25-
"sigs.k8s.io/controller-runtime/pkg/source"
26-
2724
. "github.com/onsi/ginkgo"
2825
. "github.com/onsi/gomega"
2926
appsv1 "k8s.io/api/apps/v1"
@@ -33,10 +30,13 @@ import (
3330
"k8s.io/apimachinery/pkg/runtime/schema"
3431
"k8s.io/apimachinery/pkg/types"
3532
"k8s.io/client-go/rest"
36-
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3733
"sigs.k8s.io/controller-runtime/pkg/controller"
34+
"sigs.k8s.io/controller-runtime/pkg/handler"
3835
"sigs.k8s.io/controller-runtime/pkg/manager"
3936
"sigs.k8s.io/controller-runtime/pkg/reconcile"
37+
"sigs.k8s.io/controller-runtime/pkg/scheme"
38+
"sigs.k8s.io/controller-runtime/pkg/source"
39+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
4040
)
4141

4242
var _ = Describe("application", func() {
@@ -47,7 +47,6 @@ var _ = Describe("application", func() {
4747
getConfig = func() (*rest.Config, error) { return cfg, nil }
4848
newController = controller.New
4949
newManager = manager.New
50-
getGvk = apiutil.GVKForObject
5150
})
5251

5352
AfterEach(func() {
@@ -121,6 +120,93 @@ var _ = Describe("application", func() {
121120
Expect(err.Error()).To(ContainSubstring("expected error"))
122121
Expect(instance).To(BeNil())
123122
})
123+
124+
It("should scaffold a defaulting webhook if the type implements the Defaulter interface", func() {
125+
By("creating a controller manager")
126+
m, err := manager.New(cfg, manager.Options{})
127+
Expect(err).NotTo(HaveOccurred())
128+
129+
By("registering the type in the Scheme")
130+
builder := scheme.Builder{GroupVersion: schema.GroupVersion{Group: "foo.test.org", Version: "v1"}}
131+
builder.Register(&TestDefaulter{}, &TestDefaulterList{})
132+
err = builder.AddToScheme(m.GetScheme())
133+
Expect(err).NotTo(HaveOccurred())
134+
135+
instance, err := ControllerManagedBy(m).
136+
For(&TestDefaulter{}).
137+
Owns(&appsv1.ReplicaSet{}).
138+
Build(noop)
139+
Expect(err).NotTo(HaveOccurred())
140+
Expect(instance).NotTo(BeNil())
141+
svr := m.GetWebhookServer()
142+
Expect(svr).NotTo(BeNil())
143+
144+
By("trying to register an existing mutating webhook path")
145+
path := generateMutatePath(schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestDefaulter"})
146+
Ω(func() { svr.Register(path, nil) }).Should(Panic())
147+
148+
By("registering a validating webhook path")
149+
path = generateValidatePath(schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestDefaulter"})
150+
Ω(func() { svr.Register(path, nil) }).ShouldNot(Panic())
151+
})
152+
153+
It("should scaffold a validating webhook if the type implements the Validator interface", func() {
154+
By("creating a controller manager")
155+
m, err := manager.New(cfg, manager.Options{})
156+
Expect(err).NotTo(HaveOccurred())
157+
158+
By("registering the type in the Scheme")
159+
builder := scheme.Builder{GroupVersion: schema.GroupVersion{Group: "foo.test.org", Version: "v1"}}
160+
builder.Register(&TestValidator{}, &TestValidatorList{})
161+
err = builder.AddToScheme(m.GetScheme())
162+
Expect(err).NotTo(HaveOccurred())
163+
164+
instance, err := ControllerManagedBy(m).
165+
For(&TestValidator{}).
166+
Owns(&appsv1.ReplicaSet{}).
167+
Build(noop)
168+
Expect(err).NotTo(HaveOccurred())
169+
Expect(instance).NotTo(BeNil())
170+
svr := m.GetWebhookServer()
171+
Expect(svr).NotTo(BeNil())
172+
173+
By("registering a mutating webhook path")
174+
path := generateMutatePath(schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestValidator"})
175+
Ω(func() { svr.Register(path, nil) }).ShouldNot(Panic())
176+
177+
By("trying to register an existing validating webhook path")
178+
path = generateValidatePath(schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestValidator"})
179+
Ω(func() { svr.Register(path, nil) }).Should(Panic())
180+
})
181+
182+
It("should scaffold defaulting and validating webhooks if the type implements both Defaulter and Validator interfaces", func() {
183+
By("creating a controller manager")
184+
m, err := manager.New(cfg, manager.Options{})
185+
Expect(err).NotTo(HaveOccurred())
186+
187+
By("registering the type in the Scheme")
188+
builder := scheme.Builder{GroupVersion: schema.GroupVersion{Group: "foo.test.org", Version: "v1"}}
189+
builder.Register(&TestDefaultValidator{}, &TestDefaultValidatorList{})
190+
err = builder.AddToScheme(m.GetScheme())
191+
Expect(err).NotTo(HaveOccurred())
192+
193+
instance, err := ControllerManagedBy(m).
194+
For(&TestDefaultValidator{}).
195+
Owns(&appsv1.ReplicaSet{}).
196+
Build(noop)
197+
Expect(err).NotTo(HaveOccurred())
198+
Expect(instance).NotTo(BeNil())
199+
svr := m.GetWebhookServer()
200+
Expect(svr).NotTo(BeNil())
201+
202+
By("trying to register an existing mutating webhook path")
203+
path := generateMutatePath(schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestDefaultValidator"})
204+
Ω(func() { svr.Register(path, nil) }).Should(Panic())
205+
206+
By("trying to register an existing validating webhook path")
207+
path = generateValidatePath(schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestDefaultValidator"})
208+
Ω(func() { svr.Register(path, nil) }).Should(Panic())
209+
})
124210
})
125211

126212
Describe("Start with SimpleController", func() {
@@ -281,3 +367,64 @@ type fakeType struct{}
281367

282368
func (*fakeType) GetObjectKind() schema.ObjectKind { return nil }
283369
func (*fakeType) DeepCopyObject() runtime.Object { return nil }
370+
371+
// TestDefaulter
372+
var _ runtime.Object = &TestDefaulter{}
373+
374+
type TestDefaulter struct{}
375+
376+
func (*TestDefaulter) GetObjectKind() schema.ObjectKind { return nil }
377+
func (*TestDefaulter) DeepCopyObject() runtime.Object { return nil }
378+
379+
var _ runtime.Object = &TestDefaulterList{}
380+
381+
type TestDefaulterList struct{}
382+
383+
func (*TestDefaulterList) GetObjectKind() schema.ObjectKind { return nil }
384+
func (*TestDefaulterList) DeepCopyObject() runtime.Object { return nil }
385+
386+
func (*TestDefaulter) Default() {}
387+
388+
// TestValidator
389+
var _ runtime.Object = &TestValidator{}
390+
391+
type TestValidator struct{}
392+
393+
func (*TestValidator) GetObjectKind() schema.ObjectKind { return nil }
394+
func (*TestValidator) DeepCopyObject() runtime.Object { return nil }
395+
396+
var _ runtime.Object = &TestValidatorList{}
397+
398+
type TestValidatorList struct{}
399+
400+
func (*TestValidatorList) GetObjectKind() schema.ObjectKind { return nil }
401+
func (*TestValidatorList) DeepCopyObject() runtime.Object { return nil }
402+
403+
var _ admission.Validator = &TestValidator{}
404+
405+
func (*TestValidator) ValidateCreate() error { return nil }
406+
407+
func (*TestValidator) ValidateUpdate(old runtime.Object) error { return nil }
408+
409+
// TestDefaultValidator
410+
var _ runtime.Object = &TestDefaultValidator{}
411+
412+
type TestDefaultValidator struct{}
413+
414+
func (*TestDefaultValidator) GetObjectKind() schema.ObjectKind { return nil }
415+
func (*TestDefaultValidator) DeepCopyObject() runtime.Object { return nil }
416+
417+
var _ runtime.Object = &TestDefaultValidatorList{}
418+
419+
type TestDefaultValidatorList struct{}
420+
421+
func (*TestDefaultValidatorList) GetObjectKind() schema.ObjectKind { return nil }
422+
func (*TestDefaultValidatorList) DeepCopyObject() runtime.Object { return nil }
423+
424+
func (*TestDefaultValidator) Default() {}
425+
426+
var _ admission.Validator = &TestDefaultValidator{}
427+
428+
func (*TestDefaultValidator) ValidateCreate() error { return nil }
429+
430+
func (*TestDefaultValidator) ValidateUpdate(old runtime.Object) error { return nil }

pkg/builder/builder_suite_test.go

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
. "github.com/onsi/ginkgo"
2323
. "github.com/onsi/gomega"
2424

25+
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
26+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2527
"k8s.io/client-go/rest"
2628
"sigs.k8s.io/controller-runtime/pkg/envtest"
2729
logf "sigs.k8s.io/controller-runtime/pkg/log"
@@ -42,7 +44,88 @@ var cfg *rest.Config
4244
var _ = BeforeSuite(func(done Done) {
4345
logf.SetLogger(zap.LoggerTo(GinkgoWriter, true))
4446

45-
testenv = &envtest.Environment{}
47+
testenv = &envtest.Environment{
48+
CRDs: []*apiextensionsv1beta1.CustomResourceDefinition{
49+
{
50+
TypeMeta: metav1.TypeMeta{
51+
APIVersion: "apiextensions.k8s.io",
52+
Kind: "CustomResourceDefinition",
53+
},
54+
ObjectMeta: metav1.ObjectMeta{
55+
Name: "testdefaulters.foo.test.org",
56+
},
57+
58+
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
59+
Group: "foo.test.org",
60+
Version: "v1",
61+
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
62+
Plural: "testdefaulters",
63+
Singular: "testdefaulter",
64+
Kind: "TestDefaulter",
65+
},
66+
Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
67+
{
68+
Name: "v1",
69+
Served: true,
70+
Storage: true,
71+
},
72+
},
73+
},
74+
},
75+
{
76+
TypeMeta: metav1.TypeMeta{
77+
APIVersion: "apiextensions.k8s.io",
78+
Kind: "CustomResourceDefinition",
79+
},
80+
ObjectMeta: metav1.ObjectMeta{
81+
Name: "testvalidators.foo.test.org",
82+
},
83+
84+
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
85+
Group: "foo.test.org",
86+
Version: "v1",
87+
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
88+
Plural: "testvalidators",
89+
Singular: "testvalidator",
90+
Kind: "TestValidator",
91+
},
92+
Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
93+
{
94+
Name: "v1",
95+
Served: true,
96+
Storage: true,
97+
},
98+
},
99+
},
100+
},
101+
{
102+
TypeMeta: metav1.TypeMeta{
103+
APIVersion: "apiextensions.k8s.io",
104+
Kind: "CustomResourceDefinition",
105+
},
106+
ObjectMeta: metav1.ObjectMeta{
107+
Name: "testdefaultvalidators.foo.test.org",
108+
},
109+
110+
Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
111+
Group: "foo.test.org",
112+
Version: "v1",
113+
Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
114+
Plural: "testdefaultvalidators",
115+
Singular: "testdefaultvalidator",
116+
Kind: "TestDefaultValidator",
117+
},
118+
Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
119+
{
120+
Name: "v1",
121+
Served: true,
122+
Storage: true,
123+
},
124+
},
125+
},
126+
},
127+
},
128+
}
46129

47130
var err error
48131
cfg, err = testenv.Start()

pkg/manager/manager_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,22 @@ var _ = Describe("manger.Manager", func() {
121121

122122
close(done)
123123
})
124+
125+
It("should lazily initialize a webhook server if needed", func(done Done) {
126+
By("creating a manager with options")
127+
m, err := New(cfg, Options{Port: 9443, Host: "foo.com"})
128+
Expect(err).NotTo(HaveOccurred())
129+
Expect(m).NotTo(BeNil())
130+
131+
By("checking options are passed to the webhook server")
132+
svr := m.GetWebhookServer()
133+
Expect(svr).NotTo(BeNil())
134+
Expect(svr.Port).To(Equal(9443))
135+
Expect(svr.Host).To(Equal("foo.com"))
136+
137+
close(done)
138+
})
139+
124140
Context("with leader election enabled", func() {
125141
It("should default ID to controller-runtime if ID is not set", func() {
126142
var rl resourcelock.Interface

0 commit comments

Comments
 (0)