Skip to content

Commit 0f2e574

Browse files
committed
✨ enable conversion webhook in webhook builder
Main changes: - Disable conversion webhook by default and enable only if a type implements convertible - Enhanced the test coverage to include spoke to spoke conversion
1 parent bace658 commit 0f2e574

24 files changed

+629
-225
lines changed

examples/conversion/pkg/apis/addtoscheme_jobs_v1.go

-25
This file was deleted.

examples/conversion/pkg/apis/addtoscheme_jobs_v2.go

-25
This file was deleted.

examples/conversion/pkg/apis/apis.go

-32
This file was deleted.

examples/conversion/pkg/apis/jobs/v1/doc.go

-22
This file was deleted.

examples/conversion/pkg/apis/jobs/v2/doc.go

-22
This file was deleted.

pkg/builder/webhook.go

+21-2
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,9 @@ func (blder *WebhookBuilder) registerWebhooks() error {
8787
blder.registerDefaultingWebhook()
8888
blder.registerValidatingWebhook()
8989

90-
err = conversion.CheckConvertibility(blder.mgr.GetScheme(), blder.apiType)
90+
err = blder.registerConversionWebhook()
9191
if err != nil {
92-
log.Error(err, "conversion check failed", "GVK", blder.gvk)
92+
return err
9393
}
9494
return nil
9595
}
@@ -131,7 +131,26 @@ func (blder *WebhookBuilder) registerValidatingWebhook() {
131131
}
132132
}
133133

134+
func (blder *WebhookBuilder) registerConversionWebhook() error {
135+
ok, err := conversion.IsConvertible(blder.mgr.GetScheme(), blder.apiType)
136+
if err != nil {
137+
log.Error(err, "conversion check failed", "object", blder.apiType)
138+
return err
139+
}
140+
if ok {
141+
if !blder.isAlreadyHandled("/convert") {
142+
blder.mgr.GetWebhookServer().Register("/convert", &conversion.Webhook{})
143+
}
144+
log.Info("conversion webhook enabled", "object", blder.apiType)
145+
}
146+
147+
return nil
148+
}
149+
134150
func (blder *WebhookBuilder) isAlreadyHandled(path string) bool {
151+
if blder.mgr.GetWebhookServer().WebhookMux == nil {
152+
return false
153+
}
135154
h, p := blder.mgr.GetWebhookServer().WebhookMux.Handler(&http.Request{URL: &url.URL{Path: path}})
136155
if p == path && h != nil {
137156
return true

pkg/builder/webhook_test.go

+21-3
Original file line numberDiff line numberDiff line change
@@ -281,13 +281,19 @@ type TestDefaulter struct {
281281

282282
var testDefaulterGVK = schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestDefaulter"}
283283

284-
func (*TestDefaulter) GetObjectKind() schema.ObjectKind { return nil }
284+
func (d *TestDefaulter) GetObjectKind() schema.ObjectKind { return d }
285285
func (d *TestDefaulter) DeepCopyObject() runtime.Object {
286286
return &TestDefaulter{
287287
Replica: d.Replica,
288288
}
289289
}
290290

291+
func (d *TestDefaulter) GroupVersionKind() schema.GroupVersionKind {
292+
return testDefaulterGVK
293+
}
294+
295+
func (d *TestDefaulter) SetGroupVersionKind(gvk schema.GroupVersionKind) {}
296+
291297
var _ runtime.Object = &TestDefaulterList{}
292298

293299
type TestDefaulterList struct{}
@@ -310,13 +316,19 @@ type TestValidator struct {
310316

311317
var testValidatorGVK = schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestValidator"}
312318

313-
func (*TestValidator) GetObjectKind() schema.ObjectKind { return nil }
319+
func (v *TestValidator) GetObjectKind() schema.ObjectKind { return v }
314320
func (v *TestValidator) DeepCopyObject() runtime.Object {
315321
return &TestValidator{
316322
Replica: v.Replica,
317323
}
318324
}
319325

326+
func (v *TestValidator) GroupVersionKind() schema.GroupVersionKind {
327+
return testValidatorGVK
328+
}
329+
330+
func (v *TestValidator) SetGroupVersionKind(gvk schema.GroupVersionKind) {}
331+
320332
var _ runtime.Object = &TestValidatorList{}
321333

322334
type TestValidatorList struct{}
@@ -354,13 +366,19 @@ type TestDefaultValidator struct {
354366

355367
var testDefaultValidatorGVK = schema.GroupVersionKind{Group: "foo.test.org", Version: "v1", Kind: "TestDefaultValidator"}
356368

357-
func (*TestDefaultValidator) GetObjectKind() schema.ObjectKind { return nil }
369+
func (dv *TestDefaultValidator) GetObjectKind() schema.ObjectKind { return dv }
358370
func (dv *TestDefaultValidator) DeepCopyObject() runtime.Object {
359371
return &TestDefaultValidator{
360372
Replica: dv.Replica,
361373
}
362374
}
363375

376+
func (dv *TestDefaultValidator) GroupVersionKind() schema.GroupVersionKind {
377+
return testDefaultValidatorGVK
378+
}
379+
380+
func (dv *TestDefaultValidator) SetGroupVersionKind(gvk schema.GroupVersionKind) {}
381+
364382
var _ runtime.Object = &TestDefaultValidatorList{}
365383

366384
type TestDefaultValidatorList struct{}

pkg/manager/internal.go

-2
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ import (
3838
"sigs.k8s.io/controller-runtime/pkg/recorder"
3939
"sigs.k8s.io/controller-runtime/pkg/runtime/inject"
4040
"sigs.k8s.io/controller-runtime/pkg/webhook"
41-
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
4241
)
4342

4443
const (
@@ -218,7 +217,6 @@ func (cm *controllerManager) GetWebhookServer() *webhook.Server {
218217
Port: cm.port,
219218
Host: cm.host,
220219
}
221-
cm.webhookServer.Register("/convert", &conversion.Webhook{})
222220
if err := cm.Add(cm.webhookServer); err != nil {
223221
panic("unable to add webhookServer to the controller manager")
224222
}

pkg/webhook/conversion/conversion.go

+31-17
Original file line numberDiff line numberDiff line change
@@ -174,23 +174,24 @@ func (wh *Webhook) convertViaHub(src, dst conversion.Convertible) error {
174174

175175
// getHub returns an instance of the Hub for passed-in object's group/kind.
176176
func (wh *Webhook) getHub(obj runtime.Object) (conversion.Hub, error) {
177-
gvks, _, err := wh.scheme.ObjectKinds(obj)
178-
if err != nil {
179-
return nil, fmt.Errorf("error retriving object kinds for given object : %v", err)
177+
gvks := objectGVKs(wh.scheme, obj)
178+
if len(gvks) == 0 {
179+
return nil, fmt.Errorf("error retrieving gvks for object : %v", obj)
180180
}
181181

182182
var hub conversion.Hub
183-
var isHub, hubFoundAlready bool
183+
var hubFoundAlready bool
184184
for _, gvk := range gvks {
185185
instance, err := wh.scheme.New(gvk)
186186
if err != nil {
187187
return nil, fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err)
188188
}
189-
if hub, isHub = instance.(conversion.Hub); isHub {
189+
if val, isHub := instance.(conversion.Hub); isHub {
190190
if hubFoundAlready {
191191
return nil, fmt.Errorf("multiple hub version defined for %T", obj)
192192
}
193193
hubFoundAlready = true
194+
hub = val
194195
}
195196
}
196197
return hub, nil
@@ -216,21 +217,21 @@ func (wh *Webhook) allocateDstObject(apiVersion, kind string) (runtime.Object, e
216217
return obj, nil
217218
}
218219

219-
// CheckConvertibility determines if given type is convertible or not. For a type
220+
// IsConvertible determines if given type is convertible or not. For a type
220221
// to be convertible, the group-kind needs to have a Hub type defined and all
221222
// non-hub types must be able to convert to/from Hub.
222-
func CheckConvertibility(scheme *runtime.Scheme, obj runtime.Object) error {
223+
func IsConvertible(scheme *runtime.Scheme, obj runtime.Object) (bool, error) {
223224
var hubs, spokes, nonSpokes []runtime.Object
224225

225-
gvks, _, err := scheme.ObjectKinds(obj)
226-
if err != nil {
227-
return fmt.Errorf("error retriving object kinds for given object : %v", err)
226+
gvks := objectGVKs(scheme, obj)
227+
if len(gvks) == 0 {
228+
return false, fmt.Errorf("error retrieving gvks for object : %v", obj)
228229
}
229230

230231
for _, gvk := range gvks {
231232
instance, err := scheme.New(gvk)
232233
if err != nil {
233-
return fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err)
234+
return false, fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err)
234235
}
235236

236237
if isHub(instance) {
@@ -247,32 +248,45 @@ func CheckConvertibility(scheme *runtime.Scheme, obj runtime.Object) error {
247248
}
248249

249250
if len(gvks) == 1 {
250-
return nil // single version
251+
return false, nil // single version
251252
}
252253

253254
if len(hubs) == 0 && len(spokes) == 0 {
254255
// multiple version detected with no conversion implementation. This is
255256
// true for multi-version built-in types.
256-
return nil
257+
return false, nil
257258
}
258259

259260
if len(hubs) == 1 && len(nonSpokes) == 0 { // convertible
260261
spokeVersions := []string{}
261262
for _, sp := range spokes {
262263
spokeVersions = append(spokeVersions, sp.GetObjectKind().GroupVersionKind().String())
263264
}
264-
log.V(1).Info("conversion enabled for kind", "kind",
265-
gvks[0].GroupKind(), "hub", hubs[0], "spokes", spokeVersions)
266-
return nil
265+
return true, nil
267266
}
268267

269-
return PartialImplementationError{
268+
return false, PartialImplementationError{
270269
hubs: hubs,
271270
nonSpokes: nonSpokes,
272271
spokes: spokes,
273272
}
274273
}
275274

275+
// objectGVKs returns all (Group,Version,Kind) for the Group/Kind of given object.
276+
func objectGVKs(scheme *runtime.Scheme, obj runtime.Object) []schema.GroupVersionKind {
277+
var gvks []schema.GroupVersionKind
278+
279+
objGVK := obj.GetObjectKind().GroupVersionKind()
280+
knownTypes := scheme.AllKnownTypes()
281+
282+
for gvk := range knownTypes {
283+
if objGVK.GroupKind() == gvk.GroupKind() {
284+
gvks = append(gvks, gvk)
285+
}
286+
}
287+
return gvks
288+
}
289+
276290
// PartialImplementationError represents an error due to partial conversion
277291
// implementation such as hub without spokes, multiple hubs or spokes without hub.
278292
type PartialImplementationError struct {

0 commit comments

Comments
 (0)