@@ -34,6 +34,8 @@ import (
3434 "k8s.io/apimachinery/pkg/runtime"
3535 "k8s.io/apimachinery/pkg/runtime/schema"
3636 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
37+ "sigs.k8s.io/controller-runtime/pkg/client"
38+ "sigs.k8s.io/controller-runtime/pkg/client/apiutil"
3739 "sigs.k8s.io/controller-runtime/pkg/conversion"
3840 logf "sigs.k8s.io/controller-runtime/pkg/log"
3941 conversionmetrics "sigs.k8s.io/controller-runtime/pkg/webhook/conversion/metrics"
@@ -43,14 +45,110 @@ var (
4345 log = logf .Log .WithName ("conversion-webhook" )
4446)
4547
46- func NewWebhookHandler (scheme * runtime.Scheme ) http.Handler {
47- return & webhook {scheme : scheme , decoder : NewDecoder (scheme )}
48+ type Registry struct {
49+ scheme * runtime.Scheme
50+ convertersByHubGK map [schema.GroupKind ]convertersForHub
51+ }
52+
53+ func NewRegistry (scheme * runtime.Scheme ) Registry {
54+ return Registry {
55+ scheme : scheme ,
56+ convertersByHubGK : map [schema.GroupKind ]convertersForHub {},
57+ }
58+ }
59+
60+ type convertersForHub struct {
61+ hubGVK schema.GroupVersionKind
62+ convertersBySpokeGVK map [schema.GroupVersionKind ]Converter
63+ }
64+
65+ func (r Registry ) Register (hubGVK schema.GroupVersionKind , converters ... Converter ) error {
66+ if _ , ok := r .convertersByHubGK [hubGVK .GroupKind ()]; ok {
67+ return fmt .Errorf ("converter already registered for %s" , hubGVK .GroupKind ())
68+ }
69+
70+ // TODO: validate against schema that all converters have been registred for a type (similar to previous validation)
71+
72+ r .convertersByHubGK [hubGVK .GroupKind ()] = convertersForHub {
73+ hubGVK : hubGVK ,
74+ convertersBySpokeGVK : map [schema.GroupVersionKind ]Converter {},
75+ }
76+ for _ , converter := range converters {
77+ converterHubGVK , err := apiutil .GVKForObject (converter .GetHub (), r .scheme )
78+ if err != nil {
79+ return err
80+ }
81+ if hubGVK != converterHubGVK {
82+ return fmt .Errorf ("converter GVK does not match builder gvk: FIXME" )
83+ }
84+ converterSpokeGVK , err := apiutil .GVKForObject (converter .GetSpoke (), r .scheme )
85+ if err != nil {
86+ return err
87+ }
88+ if hubGVK .GroupKind () != converterSpokeGVK .GroupKind () {
89+ return fmt .Errorf ("converter GVK does not match builder gvk: FIXME" )
90+ }
91+ r .convertersByHubGK [hubGVK .GroupKind ()].convertersBySpokeGVK [converterSpokeGVK ] = converter
92+ }
93+
94+ return nil
95+ }
96+
97+ type Converter interface {
98+ GetHub () client.Object
99+ GetSpoke () client.Object
100+ ConvertHubToSpoke (hub , spoke runtime.Object ) error
101+ ConvertSpokeToHub (hub , spoke runtime.Object ) error
102+ }
103+
104+ func NewConverter [hubObject , spokeObject client.Object ](
105+ hub hubObject ,
106+ spoke spokeObject ,
107+ convertHubToSpokeFunc func (src hubObject , dst spokeObject ) error ,
108+ convertSpokeToHubFunc func (src spokeObject , dst hubObject ) error ,
109+ ) Converter {
110+ return & converter [hubObject , spokeObject ]{
111+ hub : hub ,
112+ spoke : spoke ,
113+ convertSpokeToHubFunc : convertSpokeToHubFunc ,
114+ convertHubToSpokeFunc : convertHubToSpokeFunc ,
115+ }
116+ }
117+
118+ var _ Converter = converter [client.Object , client.Object ]{}
119+
120+ type converter [hubObject , spokeObject client.Object ] struct {
121+ hub hubObject
122+ spoke spokeObject
123+ convertHubToSpokeFunc func (src hubObject , dst spokeObject ) error
124+ convertSpokeToHubFunc func (src spokeObject , dst hubObject ) error
125+ }
126+
127+ func (c converter [hubObject , spokeObject ]) GetHub () client.Object {
128+ return c .hub
129+ }
130+
131+ func (c converter [hubObject , spokeObject ]) GetSpoke () client.Object {
132+ return c .spoke
133+ }
134+
135+ func (c converter [hubObject , spokeObject ]) ConvertHubToSpoke (hub , spoke runtime.Object ) error {
136+ return c .convertHubToSpokeFunc (hub .(hubObject ), spoke .(spokeObject ))
137+ }
138+
139+ func (c converter [hubObject , spokeObject ]) ConvertSpokeToHub (hub , spoke runtime.Object ) error {
140+ return c .convertSpokeToHubFunc (spoke .(spokeObject ), hub .(hubObject ))
141+ }
142+
143+ func NewWebhookHandler (scheme * runtime.Scheme , registry Registry ) http.Handler {
144+ return & webhook {scheme : scheme , decoder : NewDecoder (scheme ), registry : registry }
48145}
49146
50147// webhook implements a CRD conversion webhook HTTP handler.
51148type webhook struct {
52- scheme * runtime.Scheme
53- decoder * Decoder
149+ scheme * runtime.Scheme
150+ decoder * Decoder
151+ registry Registry
54152}
55153
56154// ensure Webhook implements http.Handler
@@ -149,6 +247,34 @@ func (wh *webhook) convertObject(src, dst runtime.Object) error {
149247 return fmt .Errorf ("conversion is not allowed between same type %T" , src )
150248 }
151249
250+ if converters , ok := wh .registry .convertersByHubGK [srcGVK .GroupKind ()]; ok {
251+ srcIsHub := converters .hubGVK == srcGVK
252+ dstIsHub := converters .hubGVK == dstGVK
253+ _ , srcIsConvertible := converters .convertersBySpokeGVK [srcGVK ]
254+ _ , dstIsConvertible := converters .convertersBySpokeGVK [dstGVK ]
255+
256+ switch {
257+ case srcIsHub && dstIsConvertible :
258+ return converters .convertersBySpokeGVK [dstGVK ].ConvertHubToSpoke (src , dst )
259+ case dstIsHub && srcIsConvertible :
260+ return converters .convertersBySpokeGVK [srcGVK ].ConvertSpokeToHub (src , dst )
261+ case srcIsConvertible && dstIsConvertible :
262+ hubGVK := converters .hubGVK
263+ hub , err := wh .scheme .New (hubGVK )
264+ if err != nil {
265+ return fmt .Errorf ("failed to allocate an instance for gvk %v: %w" , hubGVK , err )
266+ }
267+ if err := converters .convertersBySpokeGVK [srcGVK ].ConvertSpokeToHub (src , hub ); err != nil {
268+ return fmt .Errorf ("%T failed to convert to hub version %T : %w" , src , hub , err )
269+ }
270+ if err := converters .convertersBySpokeGVK [dstGVK ].ConvertHubToSpoke (hub , dst ); err != nil {
271+ return fmt .Errorf ("%T failed to convert from hub version %T : %w" , dst , hub , err )
272+ }
273+ default :
274+ return fmt .Errorf ("%T is not convertible to %T" , src , dst )
275+ }
276+ }
277+
152278 srcIsHub , dstIsHub := isHub (src ), isHub (dst )
153279 srcIsConvertible , dstIsConvertible := isConvertible (src ), isConvertible (dst )
154280
0 commit comments