@@ -30,7 +30,6 @@ import (
30
30
"k8s.io/apimachinery/pkg/runtime/schema"
31
31
"k8s.io/apimachinery/pkg/runtime/serializer"
32
32
"k8s.io/client-go/discovery"
33
- clientgoscheme "k8s.io/client-go/kubernetes/scheme"
34
33
"k8s.io/client-go/rest"
35
34
"k8s.io/client-go/restmapper"
36
35
)
40
39
protobufSchemeLock sync.RWMutex
41
40
)
42
41
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
-
52
42
// 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.
54
44
func AddToProtobufScheme (addToScheme func (* runtime.Scheme ) error ) error {
55
45
protobufSchemeLock .Lock ()
56
46
defer protobufSchemeLock .Unlock ()
@@ -118,8 +108,8 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi
118
108
// RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated
119
109
// with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from
120
110
// 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 ))
123
113
}
124
114
125
115
// serializerWithDecodedGVK is a CodecFactory that overrides the DecoderToVersion of a WithoutConversionCodecFactory
@@ -136,7 +126,7 @@ func (f serializerWithDecodedGVK) DecoderToVersion(serializer runtime.Decoder, _
136
126
}
137
127
138
128
// 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 {
140
130
gv := gvk .GroupVersion ()
141
131
142
132
cfg := rest .CopyConfig (baseConfig )
@@ -151,11 +141,9 @@ func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConf
151
141
}
152
142
// TODO(FillZpp): In the long run, we want to check discovery or something to make sure that this is actually true.
153
143
if cfg .ContentType == "" && ! isUnstructured {
154
- protobufSchemeLock .RLock ()
155
- if protobufScheme .Recognizes (gvk ) {
144
+ if canUseProtobuf (scheme , gvk ) {
156
145
cfg .ContentType = runtime .ContentTypeProtobuf
157
146
}
158
- protobufSchemeLock .RUnlock ()
159
147
}
160
148
161
149
if isUnstructured {
@@ -194,3 +182,129 @@ func zero(x interface{}) {
194
182
res := reflect .ValueOf (x ).Elem ()
195
183
res .Set (reflect .Zero (res .Type ()))
196
184
}
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