Skip to content

Commit b026aaa

Browse files
authored
Merge pull request #427 from droot/feature/enable-conversion-webhook
✨ enable conversion webhook by default
2 parents 6fc7baa + 8da12a6 commit b026aaa

File tree

6 files changed

+129
-5
lines changed

6 files changed

+129
-5
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ package v1
2525

2626
import (
2727
"k8s.io/apimachinery/pkg/runtime/schema"
28-
controllers "sigs.k8s.io/controller-runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/scheme"
2929
)
3030

3131
var (
3232
// SchemeGroupVersion is group version used to register these objects
3333
SchemeGroupVersion = schema.GroupVersion{Group: "jobs.example.org", Version: "v1"}
3434

3535
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
36-
SchemeBuilder = &controllers.SchemeBuilder{GroupVersion: SchemeGroupVersion}
36+
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
3737

3838
// AddToScheme is required by pkg/client/...
3939
AddToScheme = SchemeBuilder.AddToScheme

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ package v2
2525

2626
import (
2727
"k8s.io/apimachinery/pkg/runtime/schema"
28-
controllers "sigs.k8s.io/controller-runtime"
28+
"sigs.k8s.io/controller-runtime/pkg/scheme"
2929
)
3030

3131
var (
3232
// SchemeGroupVersion is group version used to register these objects
3333
SchemeGroupVersion = schema.GroupVersion{Group: "jobs.example.org", Version: "v2"}
3434

3535
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
36-
SchemeBuilder = &controllers.SchemeBuilder{GroupVersion: SchemeGroupVersion}
36+
SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion}
3737

3838
// AddToScheme is required by pkg/client/...
3939
AddToScheme = SchemeBuilder.AddToScheme

pkg/builder/build.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"sigs.k8s.io/controller-runtime/pkg/reconcile"
3333
"sigs.k8s.io/controller-runtime/pkg/source"
3434
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
35+
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
3536
)
3637

3738
// Supporting mocking out functions for testing
@@ -285,7 +286,11 @@ func (blder *Builder) doWebhook() error {
285286
}
286287
}
287288

288-
return err
289+
err = conversion.CheckConvertibility(blder.mgr.GetScheme(), blder.apiType)
290+
if err != nil {
291+
log.Error(err, "conversion check failed", "GVK", gvk)
292+
}
293+
return nil
289294
}
290295

291296
func generateMutatePath(gvk schema.GroupVersionKind) string {

pkg/manager/internal.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ 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"
4142
)
4243

4344
var log = logf.RuntimeLog.WithName("manager")
@@ -191,6 +192,7 @@ func (cm *controllerManager) GetWebhookServer() *webhook.Server {
191192
Port: cm.port,
192193
Host: cm.host,
193194
}
195+
cm.webhookServer.Register("/convert", &conversion.Webhook{})
194196
if err := cm.Add(cm.webhookServer); err != nil {
195197
panic("unable to add webhookServer to the controller manager")
196198
}

pkg/webhook/conversion/conversion.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,87 @@ func (wh *Webhook) allocateDstObject(apiVersion, kind string) (runtime.Object, e
212212
return obj, nil
213213
}
214214

215+
// CheckConvertibility determines if given type is convertible or not. For a type
216+
// to be convertible, the group-kind needs to have a Hub type defined and all
217+
// non-hub types must be able to convert to/from Hub.
218+
func CheckConvertibility(scheme *runtime.Scheme, obj runtime.Object) error {
219+
var hubs, spokes, nonSpokes []runtime.Object
220+
221+
gvks, _, err := scheme.ObjectKinds(obj)
222+
if err != nil {
223+
return fmt.Errorf("error retriving object kinds for given object : %v", err)
224+
}
225+
226+
for _, gvk := range gvks {
227+
instance, err := scheme.New(gvk)
228+
if err != nil {
229+
return fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err)
230+
}
231+
232+
if isHub(instance) {
233+
hubs = append(hubs, instance)
234+
continue
235+
}
236+
237+
if !isConvertible(instance) {
238+
nonSpokes = append(nonSpokes, instance)
239+
continue
240+
}
241+
242+
spokes = append(spokes, instance)
243+
}
244+
245+
if len(gvks) == 1 {
246+
return nil // single version
247+
}
248+
249+
if len(hubs) == 0 && len(spokes) == 0 {
250+
// multiple version detected with no conversion implementation. This is
251+
// true for multi-version built-in types.
252+
return nil
253+
}
254+
255+
if len(hubs) == 1 && len(nonSpokes) == 0 { // convertible
256+
spokeVersions := []string{}
257+
for _, sp := range spokes {
258+
spokeVersions = append(spokeVersions, sp.GetObjectKind().GroupVersionKind().String())
259+
}
260+
log.V(1).Info("conversion enabled for kind", "kind",
261+
gvks[0].GroupKind(), "hub", hubs[0], "spokes", spokeVersions)
262+
return nil
263+
}
264+
265+
return PartialImplementationError{
266+
hubs: hubs,
267+
nonSpokes: nonSpokes,
268+
spokes: spokes,
269+
}
270+
}
271+
272+
// PartialImplementationError represents an error due to partial conversion
273+
// implementation such as hub without spokes, multiple hubs or spokes without hub.
274+
type PartialImplementationError struct {
275+
gvk schema.GroupVersionKind
276+
hubs []runtime.Object
277+
nonSpokes []runtime.Object
278+
spokes []runtime.Object
279+
}
280+
281+
func (e PartialImplementationError) Error() string {
282+
if len(e.hubs) == 0 {
283+
return fmt.Sprintf("no hub defined for gvk %s", e.gvk)
284+
}
285+
if len(e.hubs) > 1 {
286+
return fmt.Sprintf("multiple(%d) hubs defined for group-kind '%s' ",
287+
len(e.hubs), e.gvk.GroupKind())
288+
}
289+
if len(e.nonSpokes) > 0 {
290+
return fmt.Sprintf("%d inconvertible types detected for group-kind '%s'",
291+
len(e.nonSpokes), e.gvk.GroupKind())
292+
}
293+
return ""
294+
}
295+
215296
// isHub determines if passed-in object is a Hub or not.
216297
func isHub(obj runtime.Object) bool {
217298
_, yes := obj.(conversion.Hub)

pkg/webhook/conversion/conversion_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,3 +200,39 @@ var _ = Describe("Conversion Webhook", func() {
200200
})
201201

202202
})
203+
204+
var _ = Describe("Convertibility Check", func() {
205+
206+
var scheme *runtime.Scheme
207+
208+
BeforeEach(func() {
209+
210+
scheme = kscheme.Scheme
211+
Expect(jobsapis.AddToScheme(scheme)).To(Succeed())
212+
213+
})
214+
215+
It("should not return error for convertible types", func() {
216+
obj := &jobsv2.ExternalJob{
217+
TypeMeta: metav1.TypeMeta{
218+
Kind: "ExternalJob",
219+
APIVersion: "jobs.example.org/v2",
220+
},
221+
}
222+
223+
err := CheckConvertibility(scheme, obj)
224+
Expect(err).NotTo(HaveOccurred())
225+
})
226+
227+
It("should not return error for a built-in multi-version type", func() {
228+
obj := &appsv1beta1.Deployment{
229+
TypeMeta: metav1.TypeMeta{
230+
Kind: "Deployment",
231+
APIVersion: "apps/v1beta1",
232+
},
233+
}
234+
235+
err := CheckConvertibility(scheme, obj)
236+
Expect(err).NotTo(HaveOccurred())
237+
})
238+
})

0 commit comments

Comments
 (0)