@@ -21,6 +21,7 @@ package apiutil
21
21
22
22
import (
23
23
"fmt"
24
+ "reflect"
24
25
"sync"
25
26
26
27
"k8s.io/apimachinery/pkg/api/meta"
@@ -29,7 +30,6 @@ import (
29
30
"k8s.io/apimachinery/pkg/runtime/schema"
30
31
"k8s.io/apimachinery/pkg/runtime/serializer"
31
32
"k8s.io/client-go/discovery"
32
- clientgoscheme "k8s.io/client-go/kubernetes/scheme"
33
33
"k8s.io/client-go/rest"
34
34
"k8s.io/client-go/restmapper"
35
35
)
39
39
protobufSchemeLock sync.RWMutex
40
40
)
41
41
42
- func init () {
43
- // Currently only enabled for built-in resources which are guaranteed to implement Protocol Buffers.
44
- // For custom resources, CRDs can not support Protocol Buffers but Aggregated API can.
45
- // See doc: https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#advanced-features-and-flexibility
46
- if err := clientgoscheme .AddToScheme (protobufScheme ); err != nil {
47
- panic (err )
48
- }
49
- }
50
-
51
42
// AddToProtobufScheme add the given SchemeBuilder into protobufScheme, which should
52
- // be additional types that do support protobuf.
43
+ // be additional types where we do want to support protobuf.
53
44
func AddToProtobufScheme (addToScheme func (* runtime.Scheme ) error ) error {
54
45
protobufSchemeLock .Lock ()
55
46
defer protobufSchemeLock .Unlock ()
@@ -117,8 +108,8 @@ func GVKForObject(obj runtime.Object, scheme *runtime.Scheme) (schema.GroupVersi
117
108
// RESTClientForGVK constructs a new rest.Interface capable of accessing the resource associated
118
109
// with the given GroupVersionKind. The REST client will be configured to use the negotiated serializer from
119
110
// baseConfig, if set, otherwise a default serializer will be set.
120
- func RESTClientForGVK (gvk schema.GroupVersionKind , isUnstructured bool , baseConfig * rest.Config , codecs serializer.CodecFactory ) (rest.Interface , error ) {
121
- 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 ))
122
113
}
123
114
124
115
// serializerWithDecodedGVK is a CodecFactory that overrides the DecoderToVersion of a WithoutConversionCodecFactory
@@ -135,7 +126,7 @@ func (f serializerWithDecodedGVK) DecoderToVersion(serializer runtime.Decoder, _
135
126
}
136
127
137
128
// createRestConfig copies the base config and updates needed fields for a new rest config.
138
- 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 {
139
130
gv := gvk .GroupVersion ()
140
131
141
132
cfg := rest .CopyConfig (baseConfig )
@@ -150,11 +141,9 @@ func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConf
150
141
}
151
142
// TODO(FillZpp): In the long run, we want to check discovery or something to make sure that this is actually true.
152
143
if cfg .ContentType == "" && ! isUnstructured {
153
- protobufSchemeLock .RLock ()
154
- if protobufScheme .Recognizes (gvk ) {
144
+ if canUseProtobuf (scheme , gvk ) {
155
145
cfg .ContentType = runtime .ContentTypeProtobuf
156
146
}
157
- protobufSchemeLock .RUnlock ()
158
147
}
159
148
160
149
if cfg .NegotiatedSerializer == nil {
@@ -169,3 +158,129 @@ func createRestConfig(gvk schema.GroupVersionKind, isUnstructured bool, baseConf
169
158
170
159
return cfg
171
160
}
161
+
162
+ // canUseProtobuf returns true if we should use protobuf encoding.
163
+ // We need two things: (1) the apiserver must support protobuf for the type,
164
+ // and (2) we must have a proto-compatible receiving go type.
165
+ // Because it's hard to know in general if apiserver supports proto for a given GVK,
166
+ // we maintain a list of built-in apigroups which do support proto;
167
+ // additional schemas can be added as proto-safe using AddToProtobufScheme.
168
+ // We check if we have a proto-compatible type by interface casting.
169
+ func canUseProtobuf (scheme * runtime.Scheme , gvk schema.GroupVersionKind ) bool {
170
+ // Check that the client supports proto for this type
171
+ gvkType , found := scheme .AllKnownTypes ()[gvk ]
172
+ if ! found {
173
+ // We aren't going to be able to deserialize proto without type information.
174
+ return false
175
+ }
176
+ if ! implementsProto (gvkType ) {
177
+ // We don't have proto information, can't parse proto
178
+ return false
179
+ }
180
+
181
+ // Check that the apiserver also supports proto for this type
182
+ serverSupportsProto := false
183
+
184
+ // Check for built-in types well-known to support proto
185
+ serverSupportsProto = isWellKnownKindThatSupportsProto (gvk )
186
+
187
+ // Check for additional types explicitly marked as supporting proto
188
+ if ! serverSupportsProto {
189
+ protobufSchemeLock .RLock ()
190
+ serverSupportsProto = protobufScheme .Recognizes (gvk )
191
+ protobufSchemeLock .RUnlock ()
192
+ }
193
+
194
+ return serverSupportsProto
195
+ }
196
+
197
+ // isWellKnownKindThatSupportsProto returns true if the gvk is a well-known Kind that supports proto encoding.
198
+ func isWellKnownKindThatSupportsProto (gvk schema.GroupVersionKind ) bool {
199
+ // corev1
200
+ if gvk .Group == "" && gvk .Version == "v1" {
201
+ return true
202
+ }
203
+
204
+ // extensions v1beta1
205
+ if gvk .Group == "extensions" && gvk .Version == "v1beta1" {
206
+ return true
207
+ }
208
+
209
+ // Generated with `kubectl api-resources -oname | grep "\." | sort | cut -f2- -d. | sort | uniq | awk '{print "case \"" $0 "\": return true" }'` (before adding any CRDs)
210
+ switch gvk .Group {
211
+ case "admissionregistration.k8s.io" :
212
+ return true
213
+ case "apiextensions.k8s.io" :
214
+ return true
215
+ case "apiregistration.k8s.io" :
216
+ return true
217
+ case "apps" :
218
+ return true
219
+ case "authentication.k8s.io" :
220
+ return true
221
+ case "authorization.k8s.io" :
222
+ return true
223
+ case "autoscaling" :
224
+ return true
225
+ case "batch" :
226
+ return true
227
+ case "certificates.k8s.io" :
228
+ return true
229
+ case "coordination.k8s.io" :
230
+ return true
231
+ case "discovery.k8s.io" :
232
+ return true
233
+ case "events.k8s.io" :
234
+ return true
235
+ case "flowcontrol.apiserver.k8s.io" :
236
+ return true
237
+ case "networking.k8s.io" :
238
+ return true
239
+ case "node.k8s.io" :
240
+ return true
241
+ case "policy" :
242
+ return true
243
+ case "rbac.authorization.k8s.io" :
244
+ return true
245
+ case "scheduling.k8s.io" :
246
+ return true
247
+ case "storage.k8s.io" :
248
+ return true
249
+ }
250
+ return false
251
+ }
252
+
253
+ var (
254
+ memoizeImplementsProto = make (map [reflect.Type ]bool )
255
+ memoizeImplementsProtoLock sync.RWMutex
256
+ )
257
+
258
+ // protoMessage is implemented by protobuf messages (of all libraries).
259
+ type protoMessage interface {
260
+ ProtoMessage ()
261
+ }
262
+
263
+ var protoMessageType = reflect .TypeOf (new (protoMessage )).Elem ()
264
+
265
+ // implementsProto returns true if the local go type supports protobuf deserialization.
266
+ func implementsProto (t reflect.Type ) bool {
267
+ memoizeImplementsProtoLock .RLock ()
268
+ result , found := memoizeImplementsProto [t ]
269
+ memoizeImplementsProtoLock .RUnlock ()
270
+
271
+ if found {
272
+ return result
273
+ }
274
+
275
+ // We normally get the raw struct e.g. v1.Pod, not &v1.Pod
276
+ if t .Kind () == reflect .Struct {
277
+ return implementsProto (reflect .PtrTo (t ))
278
+ }
279
+
280
+ result = t .Implements (protoMessageType )
281
+ memoizeImplementsProtoLock .Lock ()
282
+ memoizeImplementsProto [t ] = result
283
+ memoizeImplementsProtoLock .Unlock ()
284
+
285
+ return result
286
+ }
0 commit comments