blob: 3b254961d7a4615cfb2dbe871e0f98f85b7c53ab [file] [log] [blame]
Matteo Scandoloa4285862020-12-01 18:10:10 -08001/*
2Copyright 2014 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package runtime
18
19import (
20 "fmt"
21 "net/url"
22 "reflect"
23 "strings"
24
25 "k8s.io/apimachinery/pkg/conversion"
26 "k8s.io/apimachinery/pkg/runtime/schema"
27 "k8s.io/apimachinery/pkg/util/naming"
28 utilruntime "k8s.io/apimachinery/pkg/util/runtime"
29 "k8s.io/apimachinery/pkg/util/sets"
30)
31
32// Scheme defines methods for serializing and deserializing API objects, a type
33// registry for converting group, version, and kind information to and from Go
34// schemas, and mappings between Go schemas of different versions. A scheme is the
35// foundation for a versioned API and versioned configuration over time.
36//
37// In a Scheme, a Type is a particular Go struct, a Version is a point-in-time
38// identifier for a particular representation of that Type (typically backwards
39// compatible), a Kind is the unique name for that Type within the Version, and a
40// Group identifies a set of Versions, Kinds, and Types that evolve over time. An
41// Unversioned Type is one that is not yet formally bound to a type and is promised
42// to be backwards compatible (effectively a "v1" of a Type that does not expect
43// to break in the future).
44//
45// Schemes are not expected to change at runtime and are only threadsafe after
46// registration is complete.
47type Scheme struct {
48 // versionMap allows one to figure out the go type of an object with
49 // the given version and name.
50 gvkToType map[schema.GroupVersionKind]reflect.Type
51
52 // typeToGroupVersion allows one to find metadata for a given go object.
53 // The reflect.Type we index by should *not* be a pointer.
54 typeToGVK map[reflect.Type][]schema.GroupVersionKind
55
56 // unversionedTypes are transformed without conversion in ConvertToVersion.
57 unversionedTypes map[reflect.Type]schema.GroupVersionKind
58
59 // unversionedKinds are the names of kinds that can be created in the context of any group
60 // or version
61 // TODO: resolve the status of unversioned types.
62 unversionedKinds map[string]reflect.Type
63
64 // Map from version and resource to the corresponding func to convert
65 // resource field labels in that version to internal version.
66 fieldLabelConversionFuncs map[schema.GroupVersionKind]FieldLabelConversionFunc
67
68 // defaulterFuncs is an array of interfaces to be called with an object to provide defaulting
69 // the provided object must be a pointer.
70 defaulterFuncs map[reflect.Type]func(interface{})
71
72 // converter stores all registered conversion functions. It also has
73 // default converting behavior.
74 converter *conversion.Converter
75
76 // versionPriority is a map of groups to ordered lists of versions for those groups indicating the
77 // default priorities of these versions as registered in the scheme
78 versionPriority map[string][]string
79
80 // observedVersions keeps track of the order we've seen versions during type registration
81 observedVersions []schema.GroupVersion
82
83 // schemeName is the name of this scheme. If you don't specify a name, the stack of the NewScheme caller will be used.
84 // This is useful for error reporting to indicate the origin of the scheme.
85 schemeName string
86}
87
88// FieldLabelConversionFunc converts a field selector to internal representation.
89type FieldLabelConversionFunc func(label, value string) (internalLabel, internalValue string, err error)
90
91// NewScheme creates a new Scheme. This scheme is pluggable by default.
92func NewScheme() *Scheme {
93 s := &Scheme{
94 gvkToType: map[schema.GroupVersionKind]reflect.Type{},
95 typeToGVK: map[reflect.Type][]schema.GroupVersionKind{},
96 unversionedTypes: map[reflect.Type]schema.GroupVersionKind{},
97 unversionedKinds: map[string]reflect.Type{},
98 fieldLabelConversionFuncs: map[schema.GroupVersionKind]FieldLabelConversionFunc{},
99 defaulterFuncs: map[reflect.Type]func(interface{}){},
100 versionPriority: map[string][]string{},
101 schemeName: naming.GetNameFromCallsite(internalPackages...),
102 }
103 s.converter = conversion.NewConverter(s.nameFunc)
104
105 // Enable couple default conversions by default.
106 utilruntime.Must(RegisterEmbeddedConversions(s))
107 utilruntime.Must(RegisterStringConversions(s))
108
109 utilruntime.Must(s.RegisterInputDefaults(&map[string][]string{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields))
110 utilruntime.Must(s.RegisterInputDefaults(&url.Values{}, JSONKeyMapper, conversion.AllowDifferentFieldTypeNames|conversion.IgnoreMissingFields))
111 return s
112}
113
114// nameFunc returns the name of the type that we wish to use to determine when two types attempt
115// a conversion. Defaults to the go name of the type if the type is not registered.
116func (s *Scheme) nameFunc(t reflect.Type) string {
117 // find the preferred names for this type
118 gvks, ok := s.typeToGVK[t]
119 if !ok {
120 return t.Name()
121 }
122
123 for _, gvk := range gvks {
124 internalGV := gvk.GroupVersion()
125 internalGV.Version = APIVersionInternal // this is hacky and maybe should be passed in
126 internalGVK := internalGV.WithKind(gvk.Kind)
127
128 if internalType, exists := s.gvkToType[internalGVK]; exists {
129 return s.typeToGVK[internalType][0].Kind
130 }
131 }
132
133 return gvks[0].Kind
134}
135
136// fromScope gets the input version, desired output version, and desired Scheme
137// from a conversion.Scope.
138func (s *Scheme) fromScope(scope conversion.Scope) *Scheme {
139 return s
140}
141
142// Converter allows access to the converter for the scheme
143func (s *Scheme) Converter() *conversion.Converter {
144 return s.converter
145}
146
147// AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules.
148// Whenever an object of this type is serialized, it is serialized with the provided group version and is not
149// converted. Thus unversioned objects are expected to remain backwards compatible forever, as if they were in an
150// API group and version that would never be updated.
151//
152// TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into
153// every version with particular schemas. Resolve this method at that point.
154func (s *Scheme) AddUnversionedTypes(version schema.GroupVersion, types ...Object) {
155 s.addObservedVersion(version)
156 s.AddKnownTypes(version, types...)
157 for _, obj := range types {
158 t := reflect.TypeOf(obj).Elem()
159 gvk := version.WithKind(t.Name())
160 s.unversionedTypes[t] = gvk
161 if old, ok := s.unversionedKinds[gvk.Kind]; ok && t != old {
162 panic(fmt.Sprintf("%v.%v has already been registered as unversioned kind %q - kind name must be unique in scheme %q", old.PkgPath(), old.Name(), gvk, s.schemeName))
163 }
164 s.unversionedKinds[gvk.Kind] = t
165 }
166}
167
168// AddKnownTypes registers all types passed in 'types' as being members of version 'version'.
169// All objects passed to types should be pointers to structs. The name that go reports for
170// the struct becomes the "kind" field when encoding. Version may not be empty - use the
171// APIVersionInternal constant if you have a type that does not have a formal version.
172func (s *Scheme) AddKnownTypes(gv schema.GroupVersion, types ...Object) {
173 s.addObservedVersion(gv)
174 for _, obj := range types {
175 t := reflect.TypeOf(obj)
176 if t.Kind() != reflect.Ptr {
177 panic("All types must be pointers to structs.")
178 }
179 t = t.Elem()
180 s.AddKnownTypeWithName(gv.WithKind(t.Name()), obj)
181 }
182}
183
184// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
185// be encoded as. Useful for testing when you don't want to make multiple packages to define
186// your structs. Version may not be empty - use the APIVersionInternal constant if you have a
187// type that does not have a formal version.
188func (s *Scheme) AddKnownTypeWithName(gvk schema.GroupVersionKind, obj Object) {
189 s.addObservedVersion(gvk.GroupVersion())
190 t := reflect.TypeOf(obj)
191 if len(gvk.Version) == 0 {
192 panic(fmt.Sprintf("version is required on all types: %s %v", gvk, t))
193 }
194 if t.Kind() != reflect.Ptr {
195 panic("All types must be pointers to structs.")
196 }
197 t = t.Elem()
198 if t.Kind() != reflect.Struct {
199 panic("All types must be pointers to structs.")
200 }
201
202 if oldT, found := s.gvkToType[gvk]; found && oldT != t {
203 panic(fmt.Sprintf("Double registration of different types for %v: old=%v.%v, new=%v.%v in scheme %q", gvk, oldT.PkgPath(), oldT.Name(), t.PkgPath(), t.Name(), s.schemeName))
204 }
205
206 s.gvkToType[gvk] = t
207
208 for _, existingGvk := range s.typeToGVK[t] {
209 if existingGvk == gvk {
210 return
211 }
212 }
213 s.typeToGVK[t] = append(s.typeToGVK[t], gvk)
214
215 // if the type implements DeepCopyInto(<obj>), register a self-conversion
216 if m := reflect.ValueOf(obj).MethodByName("DeepCopyInto"); m.IsValid() && m.Type().NumIn() == 1 && m.Type().NumOut() == 0 && m.Type().In(0) == reflect.TypeOf(obj) {
217 if err := s.AddGeneratedConversionFunc(obj, obj, func(a, b interface{}, scope conversion.Scope) error {
218 // copy a to b
219 reflect.ValueOf(a).MethodByName("DeepCopyInto").Call([]reflect.Value{reflect.ValueOf(b)})
220 // clear TypeMeta to match legacy reflective conversion
221 b.(Object).GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
222 return nil
223 }); err != nil {
224 panic(err)
225 }
226 }
227}
228
229// KnownTypes returns the types known for the given version.
230func (s *Scheme) KnownTypes(gv schema.GroupVersion) map[string]reflect.Type {
231 types := make(map[string]reflect.Type)
232 for gvk, t := range s.gvkToType {
233 if gv != gvk.GroupVersion() {
234 continue
235 }
236
237 types[gvk.Kind] = t
238 }
239 return types
240}
241
242// AllKnownTypes returns the all known types.
243func (s *Scheme) AllKnownTypes() map[schema.GroupVersionKind]reflect.Type {
244 return s.gvkToType
245}
246
247// ObjectKinds returns all possible group,version,kind of the go object, true if the
248// object is considered unversioned, or an error if it's not a pointer or is unregistered.
249func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) {
250 // Unstructured objects are always considered to have their declared GVK
251 if _, ok := obj.(Unstructured); ok {
252 // we require that the GVK be populated in order to recognize the object
253 gvk := obj.GetObjectKind().GroupVersionKind()
254 if len(gvk.Kind) == 0 {
255 return nil, false, NewMissingKindErr("unstructured object has no kind")
256 }
257 if len(gvk.Version) == 0 {
258 return nil, false, NewMissingVersionErr("unstructured object has no version")
259 }
260 return []schema.GroupVersionKind{gvk}, false, nil
261 }
262
263 v, err := conversion.EnforcePtr(obj)
264 if err != nil {
265 return nil, false, err
266 }
267 t := v.Type()
268
269 gvks, ok := s.typeToGVK[t]
270 if !ok {
271 return nil, false, NewNotRegisteredErrForType(s.schemeName, t)
272 }
273 _, unversionedType := s.unversionedTypes[t]
274
275 return gvks, unversionedType, nil
276}
277
278// Recognizes returns true if the scheme is able to handle the provided group,version,kind
279// of an object.
280func (s *Scheme) Recognizes(gvk schema.GroupVersionKind) bool {
281 _, exists := s.gvkToType[gvk]
282 return exists
283}
284
285func (s *Scheme) IsUnversioned(obj Object) (bool, bool) {
286 v, err := conversion.EnforcePtr(obj)
287 if err != nil {
288 return false, false
289 }
290 t := v.Type()
291
292 if _, ok := s.typeToGVK[t]; !ok {
293 return false, false
294 }
295 _, ok := s.unversionedTypes[t]
296 return ok, true
297}
298
299// New returns a new API object of the given version and name, or an error if it hasn't
300// been registered. The version and kind fields must be specified.
301func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) {
302 if t, exists := s.gvkToType[kind]; exists {
303 return reflect.New(t).Interface().(Object), nil
304 }
305
306 if t, exists := s.unversionedKinds[kind.Kind]; exists {
307 return reflect.New(t).Interface().(Object), nil
308 }
309 return nil, NewNotRegisteredErrForKind(s.schemeName, kind)
310}
311
312// Log sets a logger on the scheme. For test purposes only
313func (s *Scheme) Log(l conversion.DebugLogger) {
314 s.converter.Debug = l
315}
316
317// AddIgnoredConversionType identifies a pair of types that should be skipped by
318// conversion (because the data inside them is explicitly dropped during
319// conversion).
320func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error {
321 return s.converter.RegisterIgnoredConversion(from, to)
322}
323
324// AddConversionFunc registers a function that converts between a and b by passing objects of those
325// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce
326// any other guarantee.
327func (s *Scheme) AddConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error {
328 return s.converter.RegisterUntypedConversionFunc(a, b, fn)
329}
330
331// AddGeneratedConversionFunc registers a function that converts between a and b by passing objects of those
332// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce
333// any other guarantee.
334func (s *Scheme) AddGeneratedConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error {
335 return s.converter.RegisterGeneratedUntypedConversionFunc(a, b, fn)
336}
337
338// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
339// of the given kind from the given version to internal version representation.
340func (s *Scheme) AddFieldLabelConversionFunc(gvk schema.GroupVersionKind, conversionFunc FieldLabelConversionFunc) error {
341 s.fieldLabelConversionFuncs[gvk] = conversionFunc
342 return nil
343}
344
345// RegisterInputDefaults sets the provided field mapping function and field matching
346// as the defaults for the provided input type. The fn may be nil, in which case no
347// mapping will happen by default. Use this method to register a mechanism for handling
348// a specific input type in conversion, such as a map[string]string to structs.
349func (s *Scheme) RegisterInputDefaults(in interface{}, fn conversion.FieldMappingFunc, defaultFlags conversion.FieldMatchingFlags) error {
350 return s.converter.RegisterInputDefaults(in, fn, defaultFlags)
351}
352
353// AddTypeDefaultingFunc registers a function that is passed a pointer to an
354// object and can default fields on the object. These functions will be invoked
355// when Default() is called. The function will never be called unless the
356// defaulted object matches srcType. If this function is invoked twice with the
357// same srcType, the fn passed to the later call will be used instead.
358func (s *Scheme) AddTypeDefaultingFunc(srcType Object, fn func(interface{})) {
359 s.defaulterFuncs[reflect.TypeOf(srcType)] = fn
360}
361
362// Default sets defaults on the provided Object.
363func (s *Scheme) Default(src Object) {
364 if fn, ok := s.defaulterFuncs[reflect.TypeOf(src)]; ok {
365 fn(src)
366 }
367}
368
369// Convert will attempt to convert in into out. Both must be pointers. For easy
370// testing of conversion functions. Returns an error if the conversion isn't
371// possible. You can call this with types that haven't been registered (for example,
372// a to test conversion of types that are nested within registered types). The
373// context interface is passed to the convertor. Convert also supports Unstructured
374// types and will convert them intelligently.
375func (s *Scheme) Convert(in, out interface{}, context interface{}) error {
376 unstructuredIn, okIn := in.(Unstructured)
377 unstructuredOut, okOut := out.(Unstructured)
378 switch {
379 case okIn && okOut:
380 // converting unstructured input to an unstructured output is a straight copy - unstructured
381 // is a "smart holder" and the contents are passed by reference between the two objects
382 unstructuredOut.SetUnstructuredContent(unstructuredIn.UnstructuredContent())
383 return nil
384
385 case okOut:
386 // if the output is an unstructured object, use the standard Go type to unstructured
387 // conversion. The object must not be internal.
388 obj, ok := in.(Object)
389 if !ok {
390 return fmt.Errorf("unable to convert object type %T to Unstructured, must be a runtime.Object", in)
391 }
392 gvks, unversioned, err := s.ObjectKinds(obj)
393 if err != nil {
394 return err
395 }
396 gvk := gvks[0]
397
398 // if no conversion is necessary, convert immediately
399 if unversioned || gvk.Version != APIVersionInternal {
400 content, err := DefaultUnstructuredConverter.ToUnstructured(in)
401 if err != nil {
402 return err
403 }
404 unstructuredOut.SetUnstructuredContent(content)
405 unstructuredOut.GetObjectKind().SetGroupVersionKind(gvk)
406 return nil
407 }
408
409 // attempt to convert the object to an external version first.
410 target, ok := context.(GroupVersioner)
411 if !ok {
412 return fmt.Errorf("unable to convert the internal object type %T to Unstructured without providing a preferred version to convert to", in)
413 }
414 // Convert is implicitly unsafe, so we don't need to perform a safe conversion
415 versioned, err := s.UnsafeConvertToVersion(obj, target)
416 if err != nil {
417 return err
418 }
419 content, err := DefaultUnstructuredConverter.ToUnstructured(versioned)
420 if err != nil {
421 return err
422 }
423 unstructuredOut.SetUnstructuredContent(content)
424 return nil
425
426 case okIn:
427 // converting an unstructured object to any type is modeled by first converting
428 // the input to a versioned type, then running standard conversions
429 typed, err := s.unstructuredToTyped(unstructuredIn)
430 if err != nil {
431 return err
432 }
433 in = typed
434 }
435
436 flags, meta := s.generateConvertMeta(in)
437 meta.Context = context
438 if flags == 0 {
439 flags = conversion.AllowDifferentFieldTypeNames
440 }
441 return s.converter.Convert(in, out, flags, meta)
442}
443
444// ConvertFieldLabel alters the given field label and value for an kind field selector from
445// versioned representation to an unversioned one or returns an error.
446func (s *Scheme) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
447 conversionFunc, ok := s.fieldLabelConversionFuncs[gvk]
448 if !ok {
449 return DefaultMetaV1FieldSelectorConversion(label, value)
450 }
451 return conversionFunc(label, value)
452}
453
454// ConvertToVersion attempts to convert an input object to its matching Kind in another
455// version within this scheme. Will return an error if the provided version does not
456// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also
457// return an error if the conversion does not result in a valid Object being
458// returned. Passes target down to the conversion methods as the Context on the scope.
459func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) {
460 return s.convertToVersion(true, in, target)
461}
462
463// UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible,
464// but does not guarantee the output object does not share fields with the input object. It attempts to be as
465// efficient as possible when doing conversion.
466func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) {
467 return s.convertToVersion(false, in, target)
468}
469
470// convertToVersion handles conversion with an optional copy.
471func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
472 var t reflect.Type
473
474 if u, ok := in.(Unstructured); ok {
475 typed, err := s.unstructuredToTyped(u)
476 if err != nil {
477 return nil, err
478 }
479
480 in = typed
481 // unstructuredToTyped returns an Object, which must be a pointer to a struct.
482 t = reflect.TypeOf(in).Elem()
483
484 } else {
485 // determine the incoming kinds with as few allocations as possible.
486 t = reflect.TypeOf(in)
487 if t.Kind() != reflect.Ptr {
488 return nil, fmt.Errorf("only pointer types may be converted: %v", t)
489 }
490 t = t.Elem()
491 if t.Kind() != reflect.Struct {
492 return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
493 }
494 }
495
496 kinds, ok := s.typeToGVK[t]
497 if !ok || len(kinds) == 0 {
498 return nil, NewNotRegisteredErrForType(s.schemeName, t)
499 }
500
501 gvk, ok := target.KindForGroupVersionKinds(kinds)
502 if !ok {
503 // try to see if this type is listed as unversioned (for legacy support)
504 // TODO: when we move to server API versions, we should completely remove the unversioned concept
505 if unversionedKind, ok := s.unversionedTypes[t]; ok {
506 if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
507 return copyAndSetTargetKind(copy, in, gvk)
508 }
509 return copyAndSetTargetKind(copy, in, unversionedKind)
510 }
511 return nil, NewNotRegisteredErrForTarget(s.schemeName, t, target)
512 }
513
514 // target wants to use the existing type, set kind and return (no conversion necessary)
515 for _, kind := range kinds {
516 if gvk == kind {
517 return copyAndSetTargetKind(copy, in, gvk)
518 }
519 }
520
521 // type is unversioned, no conversion necessary
522 if unversionedKind, ok := s.unversionedTypes[t]; ok {
523 if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
524 return copyAndSetTargetKind(copy, in, gvk)
525 }
526 return copyAndSetTargetKind(copy, in, unversionedKind)
527 }
528
529 out, err := s.New(gvk)
530 if err != nil {
531 return nil, err
532 }
533
534 if copy {
535 in = in.DeepCopyObject()
536 }
537
538 flags, meta := s.generateConvertMeta(in)
539 meta.Context = target
540 if err := s.converter.Convert(in, out, flags, meta); err != nil {
541 return nil, err
542 }
543
544 setTargetKind(out, gvk)
545 return out, nil
546}
547
548// unstructuredToTyped attempts to transform an unstructured object to a typed
549// object if possible. It will return an error if conversion is not possible, or the versioned
550// Go form of the object. Note that this conversion will lose fields.
551func (s *Scheme) unstructuredToTyped(in Unstructured) (Object, error) {
552 // the type must be something we recognize
553 gvks, _, err := s.ObjectKinds(in)
554 if err != nil {
555 return nil, err
556 }
557 typed, err := s.New(gvks[0])
558 if err != nil {
559 return nil, err
560 }
561 if err := DefaultUnstructuredConverter.FromUnstructured(in.UnstructuredContent(), typed); err != nil {
562 return nil, fmt.Errorf("unable to convert unstructured object to %v: %v", gvks[0], err)
563 }
564 return typed, nil
565}
566
567// generateConvertMeta constructs the meta value we pass to Convert.
568func (s *Scheme) generateConvertMeta(in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
569 return s.converter.DefaultMeta(reflect.TypeOf(in))
570}
571
572// copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful.
573func copyAndSetTargetKind(copy bool, obj Object, kind schema.GroupVersionKind) (Object, error) {
574 if copy {
575 obj = obj.DeepCopyObject()
576 }
577 setTargetKind(obj, kind)
578 return obj, nil
579}
580
581// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version.
582func setTargetKind(obj Object, kind schema.GroupVersionKind) {
583 if kind.Version == APIVersionInternal {
584 // internal is a special case
585 // TODO: look at removing the need to special case this
586 obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
587 return
588 }
589 obj.GetObjectKind().SetGroupVersionKind(kind)
590}
591
592// SetVersionPriority allows specifying a precise order of priority. All specified versions must be in the same group,
593// and the specified order overwrites any previously specified order for this group
594func (s *Scheme) SetVersionPriority(versions ...schema.GroupVersion) error {
595 groups := sets.String{}
596 order := []string{}
597 for _, version := range versions {
598 if len(version.Version) == 0 || version.Version == APIVersionInternal {
599 return fmt.Errorf("internal versions cannot be prioritized: %v", version)
600 }
601
602 groups.Insert(version.Group)
603 order = append(order, version.Version)
604 }
605 if len(groups) != 1 {
606 return fmt.Errorf("must register versions for exactly one group: %v", strings.Join(groups.List(), ", "))
607 }
608
609 s.versionPriority[groups.List()[0]] = order
610 return nil
611}
612
613// PrioritizedVersionsForGroup returns versions for a single group in priority order
614func (s *Scheme) PrioritizedVersionsForGroup(group string) []schema.GroupVersion {
615 ret := []schema.GroupVersion{}
616 for _, version := range s.versionPriority[group] {
617 ret = append(ret, schema.GroupVersion{Group: group, Version: version})
618 }
619 for _, observedVersion := range s.observedVersions {
620 if observedVersion.Group != group {
621 continue
622 }
623 found := false
624 for _, existing := range ret {
625 if existing == observedVersion {
626 found = true
627 break
628 }
629 }
630 if !found {
631 ret = append(ret, observedVersion)
632 }
633 }
634
635 return ret
636}
637
638// PrioritizedVersionsAllGroups returns all known versions in their priority order. Groups are random, but
639// versions for a single group are prioritized
640func (s *Scheme) PrioritizedVersionsAllGroups() []schema.GroupVersion {
641 ret := []schema.GroupVersion{}
642 for group, versions := range s.versionPriority {
643 for _, version := range versions {
644 ret = append(ret, schema.GroupVersion{Group: group, Version: version})
645 }
646 }
647 for _, observedVersion := range s.observedVersions {
648 found := false
649 for _, existing := range ret {
650 if existing == observedVersion {
651 found = true
652 break
653 }
654 }
655 if !found {
656 ret = append(ret, observedVersion)
657 }
658 }
659 return ret
660}
661
662// PreferredVersionAllGroups returns the most preferred version for every group.
663// group ordering is random.
664func (s *Scheme) PreferredVersionAllGroups() []schema.GroupVersion {
665 ret := []schema.GroupVersion{}
666 for group, versions := range s.versionPriority {
667 for _, version := range versions {
668 ret = append(ret, schema.GroupVersion{Group: group, Version: version})
669 break
670 }
671 }
672 for _, observedVersion := range s.observedVersions {
673 found := false
674 for _, existing := range ret {
675 if existing.Group == observedVersion.Group {
676 found = true
677 break
678 }
679 }
680 if !found {
681 ret = append(ret, observedVersion)
682 }
683 }
684
685 return ret
686}
687
688// IsGroupRegistered returns true if types for the group have been registered with the scheme
689func (s *Scheme) IsGroupRegistered(group string) bool {
690 for _, observedVersion := range s.observedVersions {
691 if observedVersion.Group == group {
692 return true
693 }
694 }
695 return false
696}
697
698// IsVersionRegistered returns true if types for the version have been registered with the scheme
699func (s *Scheme) IsVersionRegistered(version schema.GroupVersion) bool {
700 for _, observedVersion := range s.observedVersions {
701 if observedVersion == version {
702 return true
703 }
704 }
705
706 return false
707}
708
709func (s *Scheme) addObservedVersion(version schema.GroupVersion) {
710 if len(version.Version) == 0 || version.Version == APIVersionInternal {
711 return
712 }
713 for _, observedVersion := range s.observedVersions {
714 if observedVersion == version {
715 return
716 }
717 }
718
719 s.observedVersions = append(s.observedVersions, version)
720}
721
722func (s *Scheme) Name() string {
723 return s.schemeName
724}
725
726// internalPackages are packages that ignored when creating a default reflector name. These packages are in the common
727// call chains to NewReflector, so they'd be low entropy names for reflectors
728var internalPackages = []string{"k8s.io/apimachinery/pkg/runtime/scheme.go"}