blob: fd37e293ab1a7767cc1ea4394d0a87c5a2a1349e [file] [log] [blame]
Zack Williamse940c7a2019-08-21 14:25:39 -07001/*
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 utilruntime.Must(s.AddConversionFuncs(DefaultEmbeddedConversions()...))
106
107 // Enable map[string][]string conversions by default
108 utilruntime.Must(s.AddConversionFuncs(DefaultStringConversions...))
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
216// KnownTypes returns the types known for the given version.
217func (s *Scheme) KnownTypes(gv schema.GroupVersion) map[string]reflect.Type {
218 types := make(map[string]reflect.Type)
219 for gvk, t := range s.gvkToType {
220 if gv != gvk.GroupVersion() {
221 continue
222 }
223
224 types[gvk.Kind] = t
225 }
226 return types
227}
228
229// AllKnownTypes returns the all known types.
230func (s *Scheme) AllKnownTypes() map[schema.GroupVersionKind]reflect.Type {
231 return s.gvkToType
232}
233
234// ObjectKinds returns all possible group,version,kind of the go object, true if the
235// object is considered unversioned, or an error if it's not a pointer or is unregistered.
236func (s *Scheme) ObjectKinds(obj Object) ([]schema.GroupVersionKind, bool, error) {
237 // Unstructured objects are always considered to have their declared GVK
238 if _, ok := obj.(Unstructured); ok {
239 // we require that the GVK be populated in order to recognize the object
240 gvk := obj.GetObjectKind().GroupVersionKind()
241 if len(gvk.Kind) == 0 {
242 return nil, false, NewMissingKindErr("unstructured object has no kind")
243 }
244 if len(gvk.Version) == 0 {
245 return nil, false, NewMissingVersionErr("unstructured object has no version")
246 }
247 return []schema.GroupVersionKind{gvk}, false, nil
248 }
249
250 v, err := conversion.EnforcePtr(obj)
251 if err != nil {
252 return nil, false, err
253 }
254 t := v.Type()
255
256 gvks, ok := s.typeToGVK[t]
257 if !ok {
258 return nil, false, NewNotRegisteredErrForType(s.schemeName, t)
259 }
260 _, unversionedType := s.unversionedTypes[t]
261
262 return gvks, unversionedType, nil
263}
264
265// Recognizes returns true if the scheme is able to handle the provided group,version,kind
266// of an object.
267func (s *Scheme) Recognizes(gvk schema.GroupVersionKind) bool {
268 _, exists := s.gvkToType[gvk]
269 return exists
270}
271
272func (s *Scheme) IsUnversioned(obj Object) (bool, bool) {
273 v, err := conversion.EnforcePtr(obj)
274 if err != nil {
275 return false, false
276 }
277 t := v.Type()
278
279 if _, ok := s.typeToGVK[t]; !ok {
280 return false, false
281 }
282 _, ok := s.unversionedTypes[t]
283 return ok, true
284}
285
286// New returns a new API object of the given version and name, or an error if it hasn't
287// been registered. The version and kind fields must be specified.
288func (s *Scheme) New(kind schema.GroupVersionKind) (Object, error) {
289 if t, exists := s.gvkToType[kind]; exists {
290 return reflect.New(t).Interface().(Object), nil
291 }
292
293 if t, exists := s.unversionedKinds[kind.Kind]; exists {
294 return reflect.New(t).Interface().(Object), nil
295 }
296 return nil, NewNotRegisteredErrForKind(s.schemeName, kind)
297}
298
299// Log sets a logger on the scheme. For test purposes only
300func (s *Scheme) Log(l conversion.DebugLogger) {
301 s.converter.Debug = l
302}
303
304// AddIgnoredConversionType identifies a pair of types that should be skipped by
305// conversion (because the data inside them is explicitly dropped during
306// conversion).
307func (s *Scheme) AddIgnoredConversionType(from, to interface{}) error {
308 return s.converter.RegisterIgnoredConversion(from, to)
309}
310
311// AddConversionFuncs adds functions to the list of conversion functions. The given
312// functions should know how to convert between two of your API objects, or their
313// sub-objects. We deduce how to call these functions from the types of their two
314// parameters; see the comment for Converter.Register.
315//
316// Note that, if you need to copy sub-objects that didn't change, you can use the
317// conversion.Scope object that will be passed to your conversion function.
318// Additionally, all conversions started by Scheme will set the SrcVersion and
319// DestVersion fields on the Meta object. Example:
320//
321// s.AddConversionFuncs(
322// func(in *InternalObject, out *ExternalObject, scope conversion.Scope) error {
323// // You can depend on Meta() being non-nil, and this being set to
324// // the source version, e.g., ""
325// s.Meta().SrcVersion
326// // You can depend on this being set to the destination version,
327// // e.g., "v1".
328// s.Meta().DestVersion
329// // Call scope.Convert to copy sub-fields.
330// s.Convert(&in.SubFieldThatMoved, &out.NewLocation.NewName, 0)
331// return nil
332// },
333// )
334//
335// (For more detail about conversion functions, see Converter.Register's comment.)
336//
337// Also note that the default behavior, if you don't add a conversion function, is to
338// sanely copy fields that have the same names and same type names. It's OK if the
339// destination type has extra fields, but it must not remove any. So you only need to
340// add conversion functions for things with changed/removed fields.
341func (s *Scheme) AddConversionFuncs(conversionFuncs ...interface{}) error {
342 for _, f := range conversionFuncs {
343 if err := s.converter.RegisterConversionFunc(f); err != nil {
344 return err
345 }
346 }
347 return nil
348}
349
350// AddConversionFunc registers a function that converts between a and b by passing objects of those
351// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce
352// any other guarantee.
353func (s *Scheme) AddConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error {
354 return s.converter.RegisterUntypedConversionFunc(a, b, fn)
355}
356
357// AddGeneratedConversionFunc registers a function that converts between a and b by passing objects of those
358// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce
359// any other guarantee.
360func (s *Scheme) AddGeneratedConversionFunc(a, b interface{}, fn conversion.ConversionFunc) error {
361 return s.converter.RegisterGeneratedUntypedConversionFunc(a, b, fn)
362}
363
364// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
365// of the given kind from the given version to internal version representation.
366func (s *Scheme) AddFieldLabelConversionFunc(gvk schema.GroupVersionKind, conversionFunc FieldLabelConversionFunc) error {
367 s.fieldLabelConversionFuncs[gvk] = conversionFunc
368 return nil
369}
370
371// RegisterInputDefaults sets the provided field mapping function and field matching
372// as the defaults for the provided input type. The fn may be nil, in which case no
373// mapping will happen by default. Use this method to register a mechanism for handling
374// a specific input type in conversion, such as a map[string]string to structs.
375func (s *Scheme) RegisterInputDefaults(in interface{}, fn conversion.FieldMappingFunc, defaultFlags conversion.FieldMatchingFlags) error {
376 return s.converter.RegisterInputDefaults(in, fn, defaultFlags)
377}
378
379// AddTypeDefaultingFunc registers a function that is passed a pointer to an
380// object and can default fields on the object. These functions will be invoked
381// when Default() is called. The function will never be called unless the
382// defaulted object matches srcType. If this function is invoked twice with the
383// same srcType, the fn passed to the later call will be used instead.
384func (s *Scheme) AddTypeDefaultingFunc(srcType Object, fn func(interface{})) {
385 s.defaulterFuncs[reflect.TypeOf(srcType)] = fn
386}
387
388// Default sets defaults on the provided Object.
389func (s *Scheme) Default(src Object) {
390 if fn, ok := s.defaulterFuncs[reflect.TypeOf(src)]; ok {
391 fn(src)
392 }
393}
394
395// Convert will attempt to convert in into out. Both must be pointers. For easy
396// testing of conversion functions. Returns an error if the conversion isn't
397// possible. You can call this with types that haven't been registered (for example,
398// a to test conversion of types that are nested within registered types). The
399// context interface is passed to the convertor. Convert also supports Unstructured
400// types and will convert them intelligently.
401func (s *Scheme) Convert(in, out interface{}, context interface{}) error {
402 unstructuredIn, okIn := in.(Unstructured)
403 unstructuredOut, okOut := out.(Unstructured)
404 switch {
405 case okIn && okOut:
406 // converting unstructured input to an unstructured output is a straight copy - unstructured
407 // is a "smart holder" and the contents are passed by reference between the two objects
408 unstructuredOut.SetUnstructuredContent(unstructuredIn.UnstructuredContent())
409 return nil
410
411 case okOut:
412 // if the output is an unstructured object, use the standard Go type to unstructured
413 // conversion. The object must not be internal.
414 obj, ok := in.(Object)
415 if !ok {
416 return fmt.Errorf("unable to convert object type %T to Unstructured, must be a runtime.Object", in)
417 }
418 gvks, unversioned, err := s.ObjectKinds(obj)
419 if err != nil {
420 return err
421 }
422 gvk := gvks[0]
423
424 // if no conversion is necessary, convert immediately
425 if unversioned || gvk.Version != APIVersionInternal {
426 content, err := DefaultUnstructuredConverter.ToUnstructured(in)
427 if err != nil {
428 return err
429 }
430 unstructuredOut.SetUnstructuredContent(content)
431 unstructuredOut.GetObjectKind().SetGroupVersionKind(gvk)
432 return nil
433 }
434
435 // attempt to convert the object to an external version first.
436 target, ok := context.(GroupVersioner)
437 if !ok {
438 return fmt.Errorf("unable to convert the internal object type %T to Unstructured without providing a preferred version to convert to", in)
439 }
440 // Convert is implicitly unsafe, so we don't need to perform a safe conversion
441 versioned, err := s.UnsafeConvertToVersion(obj, target)
442 if err != nil {
443 return err
444 }
445 content, err := DefaultUnstructuredConverter.ToUnstructured(versioned)
446 if err != nil {
447 return err
448 }
449 unstructuredOut.SetUnstructuredContent(content)
450 return nil
451
452 case okIn:
453 // converting an unstructured object to any type is modeled by first converting
454 // the input to a versioned type, then running standard conversions
455 typed, err := s.unstructuredToTyped(unstructuredIn)
456 if err != nil {
457 return err
458 }
459 in = typed
460 }
461
462 flags, meta := s.generateConvertMeta(in)
463 meta.Context = context
464 if flags == 0 {
465 flags = conversion.AllowDifferentFieldTypeNames
466 }
467 return s.converter.Convert(in, out, flags, meta)
468}
469
470// ConvertFieldLabel alters the given field label and value for an kind field selector from
471// versioned representation to an unversioned one or returns an error.
472func (s *Scheme) ConvertFieldLabel(gvk schema.GroupVersionKind, label, value string) (string, string, error) {
473 conversionFunc, ok := s.fieldLabelConversionFuncs[gvk]
474 if !ok {
475 return DefaultMetaV1FieldSelectorConversion(label, value)
476 }
477 return conversionFunc(label, value)
478}
479
480// ConvertToVersion attempts to convert an input object to its matching Kind in another
481// version within this scheme. Will return an error if the provided version does not
482// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also
483// return an error if the conversion does not result in a valid Object being
484// returned. Passes target down to the conversion methods as the Context on the scope.
485func (s *Scheme) ConvertToVersion(in Object, target GroupVersioner) (Object, error) {
486 return s.convertToVersion(true, in, target)
487}
488
489// UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible,
490// but does not guarantee the output object does not share fields with the input object. It attempts to be as
491// efficient as possible when doing conversion.
492func (s *Scheme) UnsafeConvertToVersion(in Object, target GroupVersioner) (Object, error) {
493 return s.convertToVersion(false, in, target)
494}
495
496// convertToVersion handles conversion with an optional copy.
497func (s *Scheme) convertToVersion(copy bool, in Object, target GroupVersioner) (Object, error) {
498 var t reflect.Type
499
500 if u, ok := in.(Unstructured); ok {
501 typed, err := s.unstructuredToTyped(u)
502 if err != nil {
503 return nil, err
504 }
505
506 in = typed
507 // unstructuredToTyped returns an Object, which must be a pointer to a struct.
508 t = reflect.TypeOf(in).Elem()
509
510 } else {
511 // determine the incoming kinds with as few allocations as possible.
512 t = reflect.TypeOf(in)
513 if t.Kind() != reflect.Ptr {
514 return nil, fmt.Errorf("only pointer types may be converted: %v", t)
515 }
516 t = t.Elem()
517 if t.Kind() != reflect.Struct {
518 return nil, fmt.Errorf("only pointers to struct types may be converted: %v", t)
519 }
520 }
521
522 kinds, ok := s.typeToGVK[t]
523 if !ok || len(kinds) == 0 {
524 return nil, NewNotRegisteredErrForType(s.schemeName, t)
525 }
526
527 gvk, ok := target.KindForGroupVersionKinds(kinds)
528 if !ok {
529 // try to see if this type is listed as unversioned (for legacy support)
530 // TODO: when we move to server API versions, we should completely remove the unversioned concept
531 if unversionedKind, ok := s.unversionedTypes[t]; ok {
532 if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
533 return copyAndSetTargetKind(copy, in, gvk)
534 }
535 return copyAndSetTargetKind(copy, in, unversionedKind)
536 }
537 return nil, NewNotRegisteredErrForTarget(s.schemeName, t, target)
538 }
539
540 // target wants to use the existing type, set kind and return (no conversion necessary)
541 for _, kind := range kinds {
542 if gvk == kind {
543 return copyAndSetTargetKind(copy, in, gvk)
544 }
545 }
546
547 // type is unversioned, no conversion necessary
548 if unversionedKind, ok := s.unversionedTypes[t]; ok {
549 if gvk, ok := target.KindForGroupVersionKinds([]schema.GroupVersionKind{unversionedKind}); ok {
550 return copyAndSetTargetKind(copy, in, gvk)
551 }
552 return copyAndSetTargetKind(copy, in, unversionedKind)
553 }
554
555 out, err := s.New(gvk)
556 if err != nil {
557 return nil, err
558 }
559
560 if copy {
561 in = in.DeepCopyObject()
562 }
563
564 flags, meta := s.generateConvertMeta(in)
565 meta.Context = target
566 if err := s.converter.Convert(in, out, flags, meta); err != nil {
567 return nil, err
568 }
569
570 setTargetKind(out, gvk)
571 return out, nil
572}
573
574// unstructuredToTyped attempts to transform an unstructured object to a typed
575// object if possible. It will return an error if conversion is not possible, or the versioned
576// Go form of the object. Note that this conversion will lose fields.
577func (s *Scheme) unstructuredToTyped(in Unstructured) (Object, error) {
578 // the type must be something we recognize
579 gvks, _, err := s.ObjectKinds(in)
580 if err != nil {
581 return nil, err
582 }
583 typed, err := s.New(gvks[0])
584 if err != nil {
585 return nil, err
586 }
587 if err := DefaultUnstructuredConverter.FromUnstructured(in.UnstructuredContent(), typed); err != nil {
588 return nil, fmt.Errorf("unable to convert unstructured object to %v: %v", gvks[0], err)
589 }
590 return typed, nil
591}
592
593// generateConvertMeta constructs the meta value we pass to Convert.
594func (s *Scheme) generateConvertMeta(in interface{}) (conversion.FieldMatchingFlags, *conversion.Meta) {
595 return s.converter.DefaultMeta(reflect.TypeOf(in))
596}
597
598// copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful.
599func copyAndSetTargetKind(copy bool, obj Object, kind schema.GroupVersionKind) (Object, error) {
600 if copy {
601 obj = obj.DeepCopyObject()
602 }
603 setTargetKind(obj, kind)
604 return obj, nil
605}
606
607// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version.
608func setTargetKind(obj Object, kind schema.GroupVersionKind) {
609 if kind.Version == APIVersionInternal {
610 // internal is a special case
611 // TODO: look at removing the need to special case this
612 obj.GetObjectKind().SetGroupVersionKind(schema.GroupVersionKind{})
613 return
614 }
615 obj.GetObjectKind().SetGroupVersionKind(kind)
616}
617
618// SetVersionPriority allows specifying a precise order of priority. All specified versions must be in the same group,
619// and the specified order overwrites any previously specified order for this group
620func (s *Scheme) SetVersionPriority(versions ...schema.GroupVersion) error {
621 groups := sets.String{}
622 order := []string{}
623 for _, version := range versions {
624 if len(version.Version) == 0 || version.Version == APIVersionInternal {
625 return fmt.Errorf("internal versions cannot be prioritized: %v", version)
626 }
627
628 groups.Insert(version.Group)
629 order = append(order, version.Version)
630 }
631 if len(groups) != 1 {
632 return fmt.Errorf("must register versions for exactly one group: %v", strings.Join(groups.List(), ", "))
633 }
634
635 s.versionPriority[groups.List()[0]] = order
636 return nil
637}
638
639// PrioritizedVersionsForGroup returns versions for a single group in priority order
640func (s *Scheme) PrioritizedVersionsForGroup(group string) []schema.GroupVersion {
641 ret := []schema.GroupVersion{}
642 for _, version := range s.versionPriority[group] {
643 ret = append(ret, schema.GroupVersion{Group: group, Version: version})
644 }
645 for _, observedVersion := range s.observedVersions {
646 if observedVersion.Group != group {
647 continue
648 }
649 found := false
650 for _, existing := range ret {
651 if existing == observedVersion {
652 found = true
653 break
654 }
655 }
656 if !found {
657 ret = append(ret, observedVersion)
658 }
659 }
660
661 return ret
662}
663
664// PrioritizedVersionsAllGroups returns all known versions in their priority order. Groups are random, but
665// versions for a single group are prioritized
666func (s *Scheme) PrioritizedVersionsAllGroups() []schema.GroupVersion {
667 ret := []schema.GroupVersion{}
668 for group, versions := range s.versionPriority {
669 for _, version := range versions {
670 ret = append(ret, schema.GroupVersion{Group: group, Version: version})
671 }
672 }
673 for _, observedVersion := range s.observedVersions {
674 found := false
675 for _, existing := range ret {
676 if existing == observedVersion {
677 found = true
678 break
679 }
680 }
681 if !found {
682 ret = append(ret, observedVersion)
683 }
684 }
685 return ret
686}
687
688// PreferredVersionAllGroups returns the most preferred version for every group.
689// group ordering is random.
690func (s *Scheme) PreferredVersionAllGroups() []schema.GroupVersion {
691 ret := []schema.GroupVersion{}
692 for group, versions := range s.versionPriority {
693 for _, version := range versions {
694 ret = append(ret, schema.GroupVersion{Group: group, Version: version})
695 break
696 }
697 }
698 for _, observedVersion := range s.observedVersions {
699 found := false
700 for _, existing := range ret {
701 if existing.Group == observedVersion.Group {
702 found = true
703 break
704 }
705 }
706 if !found {
707 ret = append(ret, observedVersion)
708 }
709 }
710
711 return ret
712}
713
714// IsGroupRegistered returns true if types for the group have been registered with the scheme
715func (s *Scheme) IsGroupRegistered(group string) bool {
716 for _, observedVersion := range s.observedVersions {
717 if observedVersion.Group == group {
718 return true
719 }
720 }
721 return false
722}
723
724// IsVersionRegistered returns true if types for the version have been registered with the scheme
725func (s *Scheme) IsVersionRegistered(version schema.GroupVersion) bool {
726 for _, observedVersion := range s.observedVersions {
727 if observedVersion == version {
728 return true
729 }
730 }
731
732 return false
733}
734
735func (s *Scheme) addObservedVersion(version schema.GroupVersion) {
736 if len(version.Version) == 0 || version.Version == APIVersionInternal {
737 return
738 }
739 for _, observedVersion := range s.observedVersions {
740 if observedVersion == version {
741 return
742 }
743 }
744
745 s.observedVersions = append(s.observedVersions, version)
746}
747
748func (s *Scheme) Name() string {
749 return s.schemeName
750}
751
752// internalPackages are packages that ignored when creating a default reflector name. These packages are in the common
753// call chains to NewReflector, so they'd be low entropy names for reflectors
754var internalPackages = []string{"k8s.io/apimachinery/pkg/runtime/scheme.go"}