@@ -30,7 +30,6 @@ import (
3030 "k8s.io/apimachinery/pkg/runtime/schema"
3131 "k8s.io/apimachinery/pkg/runtime/serializer"
3232 "k8s.io/client-go/discovery"
33- clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3433 "k8s.io/client-go/rest"
3534 "k8s.io/client-go/restmapper"
3635)
4039 protobufSchemeLock sync.RWMutex
4140)
4241
43- func init () {
44- // Currently only enabled for built-in resources which are guaranteed to implement Protocol Buffers.
45- // For custom resources, CRDs can not support Protocol Buffers but Aggregated API can.
46- // See doc: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#advanced-features-and-flexibility
47- if err := clientgoscheme .AddToScheme (protobufScheme ); err != nil {
48- panic (err )
49- }
50- }
51-
5242// AddToProtobufScheme add the given SchemeBuilder into protobufScheme, which should
53- // be additional types that do support protobuf.
43+ // be additional types where we do want to support protobuf.
5444func AddToProtobufScheme (addToScheme func (* runtime.Scheme ) error ) error {
5545 protobufSchemeLock .Lock ()
5646 defer protobufSchemeLock .Unlock ()
@@ -118,8 +108,8 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi
118108// RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated
119109// with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from
120110// baseConfig, if set, otherwise a default serializer will be set.
121- func RESTClientForGVK (gvk schema.GroupVersionKind , isUnstructured bool , baseConfig * rest.Config , codecs serializer.CodecFactory ) (rest.Interface , error ) {
122- return rest .RESTClientFor (createRestConfig (gvk , isUnstructured , baseConfig , codecs ))
111+ func RESTClientForGVK (scheme * runtime. Scheme , gvk schema.GroupVersionKind , isUnstructured bool , baseConfig * rest.Config , codecs serializer.CodecFactory ) (rest.Interface , error ) {
112+ return rest .RESTClientFor (createRestConfig (scheme , gvk , isUnstructured , baseConfig , codecs ))
123113}
124114
125115// serializerWithDecodedGVK is a CodecFactory that overrides the DecoderToVersion of a WithoutConversionCodecFactory
@@ -136,7 +126,7 @@ func (f serializerWithDecodedGVK) DecoderToVersion(serializer runtime.Decoder, _
136126}
137127
138128// createRestConfig copies the base config and updates needed fields for a new rest config.
139- func createRestConfig (gvk schema.GroupVersionKind , isUnstructured bool , baseConfig * rest.Config , codecs serializer.CodecFactory ) * rest.Config {
129+ func createRestConfig (scheme * runtime. Scheme , gvk schema.GroupVersionKind , isUnstructured bool , baseConfig * rest.Config , codecs serializer.CodecFactory ) * rest.Config {
140130 gv := gvk .GroupVersion ()
141131
142132 cfg := rest .CopyConfig (baseConfig )
@@ -151,11 +141,9 @@ func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConf
151141 }
152142 // TODO(FillZpp): In the long run, we want to check discovery or something to make sure that this is actually true.
153143 if cfg .ContentType == "" && ! isUnstructured {
154- protobufSchemeLock .RLock ()
155- if protobufScheme .Recognizes (gvk ) {
144+ if canUseProtobuf (scheme , gvk ) {
156145 cfg .ContentType = runtime .ContentTypeProtobuf
157146 }
158- protobufSchemeLock .RUnlock ()
159147 }
160148
161149 if isUnstructured {
@@ -194,3 +182,129 @@ func zero(x interface{}) {
194182 res := reflect .ValueOf (x ).Elem ()
195183 res .Set (reflect .Zero (res .Type ()))
196184}
185+
186+ // canUseProtobuf returns true if we should use protobuf encoding.
187+ // We need two things: (1) the apiserver must support protobuf for the type,
188+ // and (2) we must have a proto-compatible receiving go type.
189+ // Because it's hard to know in general if apiserver supports proto for a given GVK,
190+ // we maintain a list of built-in apigroups which do support proto;
191+ // additional schemas can be added as proto-safe using AddToProtobufScheme.
192+ // We check if we have a proto-compatible type by interface casting.
193+ func canUseProtobuf (scheme * runtime.Scheme , gvk schema.GroupVersionKind ) bool {
194+ // Check that the client supports proto for this type
195+ gvkType , found := scheme .AllKnownTypes ()[gvk ]
196+ if ! found {
197+ // We aren't going to be able to deserialize proto without type information.
198+ return false
199+ }
200+ if ! implementsProto (gvkType ) {
201+ // We don't have proto information, can't parse proto
202+ return false
203+ }
204+
205+ // Check that the apiserver also supports proto for this type
206+ serverSupportsProto := false
207+
208+ // Check for built-in types well-known to support proto
209+ serverSupportsProto = isWellKnownKindThatSupportsProto (gvk )
210+
211+ // Check for additional types explicitly marked as supporting proto
212+ if ! serverSupportsProto {
213+ protobufSchemeLock .RLock ()
214+ serverSupportsProto = protobufScheme .Recognizes (gvk )
215+ protobufSchemeLock .RUnlock ()
216+ }
217+
218+ return serverSupportsProto
219+ }
220+
221+ // isWellKnownKindThatSupportsProto returns true if the gvk is a well-known Kind that supports proto encoding.
222+ func isWellKnownKindThatSupportsProto (gvk schema.GroupVersionKind ) bool {
223+ // corev1
224+ if gvk .Group == "" && gvk .Version == "v1" {
225+ return true
226+ }
227+
228+ // extensions v1beta1
229+ if gvk .Group == "extensions" && gvk .Version == "v1beta1" {
230+ return true
231+ }
232+
233+ // Generated with `kubectl api-resources -oname | grep "\." | sort | cut -f2- -d. | sort | uniq | awk '{print "case \"" $0 "\": return true" }'` (before adding any CRDs)
234+ switch gvk .Group {
235+ case "admissionregistration.k8s.io" :
236+ return true
237+ case "apiextensions.k8s.io" :
238+ return true
239+ case "apiregistration.k8s.io" :
240+ return true
241+ case "apps" :
242+ return true
243+ case "authentication.k8s.io" :
244+ return true
245+ case "authorization.k8s.io" :
246+ return true
247+ case "autoscaling" :
248+ return true
249+ case "batch" :
250+ return true
251+ case "certificates.k8s.io" :
252+ return true
253+ case "coordination.k8s.io" :
254+ return true
255+ case "discovery.k8s.io" :
256+ return true
257+ case "events.k8s.io" :
258+ return true
259+ case "flowcontrol.apiserver.k8s.io" :
260+ return true
261+ case "networking.k8s.io" :
262+ return true
263+ case "node.k8s.io" :
264+ return true
265+ case "policy" :
266+ return true
267+ case "rbac.authorization.k8s.io" :
268+ return true
269+ case "scheduling.k8s.io" :
270+ return true
271+ case "storage.k8s.io" :
272+ return true
273+ }
274+ return false
275+ }
276+
277+ var (
278+ memoizeImplementsProto = make (map [reflect.Type ]bool )
279+ memoizeImplementsProtoLock sync.RWMutex
280+ )
281+
282+ // protoMessage is implemented by protobuf messages (of all libraries).
283+ type protoMessage interface {
284+ ProtoMessage ()
285+ }
286+
287+ var protoMessageType = reflect .TypeOf (new (protoMessage )).Elem ()
288+
289+ // implementsProto returns true if the local go type supports protobuf deserialization.
290+ func implementsProto (t reflect.Type ) bool {
291+ memoizeImplementsProtoLock .RLock ()
292+ result , found := memoizeImplementsProto [t ]
293+ memoizeImplementsProtoLock .RUnlock ()
294+
295+ if found {
296+ return result
297+ }
298+
299+ // We normally get the raw struct e.g. v1.Pod, not &v1.Pod
300+ if t .Kind () == reflect .Struct {
301+ return implementsProto (reflect .PtrTo (t ))
302+ }
303+
304+ result = t .Implements (protoMessageType )
305+ memoizeImplementsProtoLock .Lock ()
306+ memoizeImplementsProto [t ] = result
307+ memoizeImplementsProtoLock .Unlock ()
308+
309+ return result
310+ }
0 commit comments