2017-11-20 09:30:52 -05:00
/ *
Copyright 2014 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package runtime
import (
"fmt"
"net/url"
"reflect"
2018-07-25 04:17:02 -04:00
"strings"
2017-11-20 09:30:52 -05:00
"k8s.io/apimachinery/pkg/conversion"
"k8s.io/apimachinery/pkg/runtime/schema"
2019-03-25 08:03:02 -04:00
"k8s.io/apimachinery/pkg/util/naming"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
2018-07-25 04:17:02 -04:00
"k8s.io/apimachinery/pkg/util/sets"
2017-11-20 09:30:52 -05:00
)
// Scheme defines methods for serializing and deserializing API objects, a type
// registry for converting group, version, and kind information to and from Go
// schemas, and mappings between Go schemas of different versions. A scheme is the
// foundation for a versioned API and versioned configuration over time.
//
// In a Scheme, a Type is a particular Go struct, a Version is a point-in-time
// identifier for a particular representation of that Type (typically backwards
// compatible), a Kind is the unique name for that Type within the Version, and a
// Group identifies a set of Versions, Kinds, and Types that evolve over time. An
// Unversioned Type is one that is not yet formally bound to a type and is promised
// to be backwards compatible (effectively a "v1" of a Type that does not expect
// to break in the future).
//
// Schemes are not expected to change at runtime and are only threadsafe after
// registration is complete.
type Scheme struct {
// versionMap allows one to figure out the go type of an object with
// the given version and name.
gvkToType map [ schema . GroupVersionKind ] reflect . Type
// typeToGroupVersion allows one to find metadata for a given go object.
// The reflect.Type we index by should *not* be a pointer.
typeToGVK map [ reflect . Type ] [ ] schema . GroupVersionKind
// unversionedTypes are transformed without conversion in ConvertToVersion.
unversionedTypes map [ reflect . Type ] schema . GroupVersionKind
// unversionedKinds are the names of kinds that can be created in the context of any group
// or version
// TODO: resolve the status of unversioned types.
unversionedKinds map [ string ] reflect . Type
// Map from version and resource to the corresponding func to convert
// resource field labels in that version to internal version.
2019-03-25 08:03:02 -04:00
fieldLabelConversionFuncs map [ schema . GroupVersionKind ] FieldLabelConversionFunc
2017-11-20 09:30:52 -05:00
// defaulterFuncs is an array of interfaces to be called with an object to provide defaulting
// the provided object must be a pointer.
defaulterFuncs map [ reflect . Type ] func ( interface { } )
// converter stores all registered conversion functions. It also has
2019-03-25 08:03:02 -04:00
// default converting behavior.
2017-11-20 09:30:52 -05:00
converter * conversion . Converter
2018-07-25 04:17:02 -04:00
// versionPriority is a map of groups to ordered lists of versions for those groups indicating the
// default priorities of these versions as registered in the scheme
versionPriority map [ string ] [ ] string
// observedVersions keeps track of the order we've seen versions during type registration
observedVersions [ ] schema . GroupVersion
2019-03-25 08:03:02 -04:00
// schemeName is the name of this scheme. If you don't specify a name, the stack of the NewScheme caller will be used.
// This is useful for error reporting to indicate the origin of the scheme.
schemeName string
2017-11-20 09:30:52 -05:00
}
2019-03-25 08:03:02 -04:00
// FieldLabelConversionFunc converts a field selector to internal representation.
2017-11-20 09:30:52 -05:00
type FieldLabelConversionFunc func ( label , value string ) ( internalLabel , internalValue string , err error )
// NewScheme creates a new Scheme. This scheme is pluggable by default.
func NewScheme ( ) * Scheme {
s := & Scheme {
2018-07-25 04:17:02 -04:00
gvkToType : map [ schema . GroupVersionKind ] reflect . Type { } ,
typeToGVK : map [ reflect . Type ] [ ] schema . GroupVersionKind { } ,
unversionedTypes : map [ reflect . Type ] schema . GroupVersionKind { } ,
unversionedKinds : map [ string ] reflect . Type { } ,
2019-03-25 08:03:02 -04:00
fieldLabelConversionFuncs : map [ schema . GroupVersionKind ] FieldLabelConversionFunc { } ,
2017-11-20 09:30:52 -05:00
defaulterFuncs : map [ reflect . Type ] func ( interface { } ) { } ,
2018-07-25 04:17:02 -04:00
versionPriority : map [ string ] [ ] string { } ,
2019-03-25 08:03:02 -04:00
schemeName : naming . GetNameFromCallsite ( internalPackages ... ) ,
2017-11-20 09:30:52 -05:00
}
s . converter = conversion . NewConverter ( s . nameFunc )
2019-03-25 08:03:02 -04:00
utilruntime . Must ( s . AddConversionFuncs ( DefaultEmbeddedConversions ( ) ... ) )
2017-11-20 09:30:52 -05:00
// Enable map[string][]string conversions by default
2019-03-25 08:03:02 -04:00
utilruntime . Must ( s . AddConversionFuncs ( DefaultStringConversions ... ) )
utilruntime . Must ( s . RegisterInputDefaults ( & map [ string ] [ ] string { } , JSONKeyMapper , conversion . AllowDifferentFieldTypeNames | conversion . IgnoreMissingFields ) )
utilruntime . Must ( s . RegisterInputDefaults ( & url . Values { } , JSONKeyMapper , conversion . AllowDifferentFieldTypeNames | conversion . IgnoreMissingFields ) )
2017-11-20 09:30:52 -05:00
return s
}
// nameFunc returns the name of the type that we wish to use to determine when two types attempt
// a conversion. Defaults to the go name of the type if the type is not registered.
func ( s * Scheme ) nameFunc ( t reflect . Type ) string {
// find the preferred names for this type
gvks , ok := s . typeToGVK [ t ]
if ! ok {
return t . Name ( )
}
for _ , gvk := range gvks {
internalGV := gvk . GroupVersion ( )
2018-07-25 04:17:02 -04:00
internalGV . Version = APIVersionInternal // this is hacky and maybe should be passed in
2017-11-20 09:30:52 -05:00
internalGVK := internalGV . WithKind ( gvk . Kind )
if internalType , exists := s . gvkToType [ internalGVK ] ; exists {
return s . typeToGVK [ internalType ] [ 0 ] . Kind
}
}
return gvks [ 0 ] . Kind
}
// fromScope gets the input version, desired output version, and desired Scheme
// from a conversion.Scope.
func ( s * Scheme ) fromScope ( scope conversion . Scope ) * Scheme {
return s
}
// Converter allows access to the converter for the scheme
func ( s * Scheme ) Converter ( ) * conversion . Converter {
return s . converter
}
// AddUnversionedTypes registers the provided types as "unversioned", which means that they follow special rules.
// Whenever an object of this type is serialized, it is serialized with the provided group version and is not
// converted. Thus unversioned objects are expected to remain backwards compatible forever, as if they were in an
// API group and version that would never be updated.
//
// TODO: there is discussion about removing unversioned and replacing it with objects that are manifest into
// every version with particular schemas. Resolve this method at that point.
func ( s * Scheme ) AddUnversionedTypes ( version schema . GroupVersion , types ... Object ) {
2018-07-25 04:17:02 -04:00
s . addObservedVersion ( version )
2017-11-20 09:30:52 -05:00
s . AddKnownTypes ( version , types ... )
for _ , obj := range types {
t := reflect . TypeOf ( obj ) . Elem ( )
gvk := version . WithKind ( t . Name ( ) )
s . unversionedTypes [ t ] = gvk
if old , ok := s . unversionedKinds [ gvk . Kind ] ; ok && t != old {
2019-03-25 08:03:02 -04:00
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 ) )
2017-11-20 09:30:52 -05:00
}
s . unversionedKinds [ gvk . Kind ] = t
}
}
// AddKnownTypes registers all types passed in 'types' as being members of version 'version'.
// All objects passed to types should be pointers to structs. The name that go reports for
// the struct becomes the "kind" field when encoding. Version may not be empty - use the
// APIVersionInternal constant if you have a type that does not have a formal version.
func ( s * Scheme ) AddKnownTypes ( gv schema . GroupVersion , types ... Object ) {
2018-07-25 04:17:02 -04:00
s . addObservedVersion ( gv )
2017-11-20 09:30:52 -05:00
for _ , obj := range types {
t := reflect . TypeOf ( obj )
if t . Kind ( ) != reflect . Ptr {
panic ( "All types must be pointers to structs." )
}
t = t . Elem ( )
s . AddKnownTypeWithName ( gv . WithKind ( t . Name ( ) ) , obj )
}
}
// AddKnownTypeWithName is like AddKnownTypes, but it lets you specify what this type should
// be encoded as. Useful for testing when you don't want to make multiple packages to define
// your structs. Version may not be empty - use the APIVersionInternal constant if you have a
// type that does not have a formal version.
func ( s * Scheme ) AddKnownTypeWithName ( gvk schema . GroupVersionKind , obj Object ) {
2018-07-25 04:17:02 -04:00
s . addObservedVersion ( gvk . GroupVersion ( ) )
2017-11-20 09:30:52 -05:00
t := reflect . TypeOf ( obj )
if len ( gvk . Version ) == 0 {
panic ( fmt . Sprintf ( "version is required on all types: %s %v" , gvk , t ) )
}
if t . Kind ( ) != reflect . Ptr {
panic ( "All types must be pointers to structs." )
}
t = t . Elem ( )
if t . Kind ( ) != reflect . Struct {
panic ( "All types must be pointers to structs." )
}
if oldT , found := s . gvkToType [ gvk ] ; found && oldT != t {
2019-03-25 08:03:02 -04:00
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 ) )
2017-11-20 09:30:52 -05:00
}
s . gvkToType [ gvk ] = t
for _ , existingGvk := range s . typeToGVK [ t ] {
if existingGvk == gvk {
return
}
}
s . typeToGVK [ t ] = append ( s . typeToGVK [ t ] , gvk )
}
// KnownTypes returns the types known for the given version.
func ( s * Scheme ) KnownTypes ( gv schema . GroupVersion ) map [ string ] reflect . Type {
types := make ( map [ string ] reflect . Type )
for gvk , t := range s . gvkToType {
if gv != gvk . GroupVersion ( ) {
continue
}
types [ gvk . Kind ] = t
}
return types
}
// AllKnownTypes returns the all known types.
func ( s * Scheme ) AllKnownTypes ( ) map [ schema . GroupVersionKind ] reflect . Type {
return s . gvkToType
}
// ObjectKinds returns all possible group,version,kind of the go object, true if the
// object is considered unversioned, or an error if it's not a pointer or is unregistered.
func ( s * Scheme ) ObjectKinds ( obj Object ) ( [ ] schema . GroupVersionKind , bool , error ) {
2018-07-25 04:17:02 -04:00
// Unstructured objects are always considered to have their declared GVK
if _ , ok := obj . ( Unstructured ) ; ok {
// we require that the GVK be populated in order to recognize the object
gvk := obj . GetObjectKind ( ) . GroupVersionKind ( )
if len ( gvk . Kind ) == 0 {
return nil , false , NewMissingKindErr ( "unstructured object has no kind" )
}
if len ( gvk . Version ) == 0 {
return nil , false , NewMissingVersionErr ( "unstructured object has no version" )
}
return [ ] schema . GroupVersionKind { gvk } , false , nil
}
2017-11-20 09:30:52 -05:00
v , err := conversion . EnforcePtr ( obj )
if err != nil {
return nil , false , err
}
t := v . Type ( )
gvks , ok := s . typeToGVK [ t ]
if ! ok {
2019-03-25 08:03:02 -04:00
return nil , false , NewNotRegisteredErrForType ( s . schemeName , t )
2017-11-20 09:30:52 -05:00
}
_ , unversionedType := s . unversionedTypes [ t ]
return gvks , unversionedType , nil
}
// Recognizes returns true if the scheme is able to handle the provided group,version,kind
// of an object.
func ( s * Scheme ) Recognizes ( gvk schema . GroupVersionKind ) bool {
_ , exists := s . gvkToType [ gvk ]
return exists
}
func ( s * Scheme ) IsUnversioned ( obj Object ) ( bool , bool ) {
v , err := conversion . EnforcePtr ( obj )
if err != nil {
return false , false
}
t := v . Type ( )
if _ , ok := s . typeToGVK [ t ] ; ! ok {
return false , false
}
_ , ok := s . unversionedTypes [ t ]
return ok , true
}
// New returns a new API object of the given version and name, or an error if it hasn't
// been registered. The version and kind fields must be specified.
func ( s * Scheme ) New ( kind schema . GroupVersionKind ) ( Object , error ) {
if t , exists := s . gvkToType [ kind ] ; exists {
return reflect . New ( t ) . Interface ( ) . ( Object ) , nil
}
if t , exists := s . unversionedKinds [ kind . Kind ] ; exists {
return reflect . New ( t ) . Interface ( ) . ( Object ) , nil
}
2019-03-25 08:03:02 -04:00
return nil , NewNotRegisteredErrForKind ( s . schemeName , kind )
2017-11-20 09:30:52 -05:00
}
// Log sets a logger on the scheme. For test purposes only
func ( s * Scheme ) Log ( l conversion . DebugLogger ) {
s . converter . Debug = l
}
// AddIgnoredConversionType identifies a pair of types that should be skipped by
// conversion (because the data inside them is explicitly dropped during
// conversion).
func ( s * Scheme ) AddIgnoredConversionType ( from , to interface { } ) error {
return s . converter . RegisterIgnoredConversion ( from , to )
}
// AddConversionFuncs adds functions to the list of conversion functions. The given
// functions should know how to convert between two of your API objects, or their
// sub-objects. We deduce how to call these functions from the types of their two
// parameters; see the comment for Converter.Register.
//
// Note that, if you need to copy sub-objects that didn't change, you can use the
// conversion.Scope object that will be passed to your conversion function.
// Additionally, all conversions started by Scheme will set the SrcVersion and
// DestVersion fields on the Meta object. Example:
//
// s.AddConversionFuncs(
// func(in *InternalObject, out *ExternalObject, scope conversion.Scope) error {
// // You can depend on Meta() being non-nil, and this being set to
// // the source version, e.g., ""
// s.Meta().SrcVersion
// // You can depend on this being set to the destination version,
// // e.g., "v1".
// s.Meta().DestVersion
// // Call scope.Convert to copy sub-fields.
// s.Convert(&in.SubFieldThatMoved, &out.NewLocation.NewName, 0)
// return nil
// },
// )
//
// (For more detail about conversion functions, see Converter.Register's comment.)
//
// Also note that the default behavior, if you don't add a conversion function, is to
// sanely copy fields that have the same names and same type names. It's OK if the
// destination type has extra fields, but it must not remove any. So you only need to
// add conversion functions for things with changed/removed fields.
func ( s * Scheme ) AddConversionFuncs ( conversionFuncs ... interface { } ) error {
for _ , f := range conversionFuncs {
if err := s . converter . RegisterConversionFunc ( f ) ; err != nil {
return err
}
}
return nil
}
2019-03-25 08:03:02 -04:00
// AddConversionFunc registers a function that converts between a and b by passing objects of those
// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce
// any other guarantee.
func ( s * Scheme ) AddConversionFunc ( a , b interface { } , fn conversion . ConversionFunc ) error {
return s . converter . RegisterUntypedConversionFunc ( a , b , fn )
}
// AddGeneratedConversionFunc registers a function that converts between a and b by passing objects of those
// types to the provided function. The function *must* accept objects of a and b - this machinery will not enforce
// any other guarantee.
func ( s * Scheme ) AddGeneratedConversionFunc ( a , b interface { } , fn conversion . ConversionFunc ) error {
return s . converter . RegisterGeneratedUntypedConversionFunc ( a , b , fn )
2017-11-20 09:30:52 -05:00
}
// AddFieldLabelConversionFunc adds a conversion function to convert field selectors
// of the given kind from the given version to internal version representation.
2019-03-25 08:03:02 -04:00
func ( s * Scheme ) AddFieldLabelConversionFunc ( gvk schema . GroupVersionKind , conversionFunc FieldLabelConversionFunc ) error {
s . fieldLabelConversionFuncs [ gvk ] = conversionFunc
2017-11-20 09:30:52 -05:00
return nil
}
// RegisterInputDefaults sets the provided field mapping function and field matching
// as the defaults for the provided input type. The fn may be nil, in which case no
// mapping will happen by default. Use this method to register a mechanism for handling
// a specific input type in conversion, such as a map[string]string to structs.
func ( s * Scheme ) RegisterInputDefaults ( in interface { } , fn conversion . FieldMappingFunc , defaultFlags conversion . FieldMatchingFlags ) error {
return s . converter . RegisterInputDefaults ( in , fn , defaultFlags )
}
2019-03-25 08:03:02 -04:00
// AddTypeDefaultingFunc registers a function that is passed a pointer to an
2017-11-20 09:30:52 -05:00
// object and can default fields on the object. These functions will be invoked
// when Default() is called. The function will never be called unless the
// defaulted object matches srcType. If this function is invoked twice with the
// same srcType, the fn passed to the later call will be used instead.
func ( s * Scheme ) AddTypeDefaultingFunc ( srcType Object , fn func ( interface { } ) ) {
s . defaulterFuncs [ reflect . TypeOf ( srcType ) ] = fn
}
// Default sets defaults on the provided Object.
func ( s * Scheme ) Default ( src Object ) {
if fn , ok := s . defaulterFuncs [ reflect . TypeOf ( src ) ] ; ok {
fn ( src )
}
}
// Convert will attempt to convert in into out. Both must be pointers. For easy
// testing of conversion functions. Returns an error if the conversion isn't
// possible. You can call this with types that haven't been registered (for example,
// a to test conversion of types that are nested within registered types). The
2018-07-25 04:17:02 -04:00
// context interface is passed to the convertor. Convert also supports Unstructured
// types and will convert them intelligently.
2017-11-20 09:30:52 -05:00
func ( s * Scheme ) Convert ( in , out interface { } , context interface { } ) error {
2018-07-25 04:17:02 -04:00
unstructuredIn , okIn := in . ( Unstructured )
unstructuredOut , okOut := out . ( Unstructured )
switch {
case okIn && okOut :
// converting unstructured input to an unstructured output is a straight copy - unstructured
// is a "smart holder" and the contents are passed by reference between the two objects
unstructuredOut . SetUnstructuredContent ( unstructuredIn . UnstructuredContent ( ) )
return nil
case okOut :
// if the output is an unstructured object, use the standard Go type to unstructured
// conversion. The object must not be internal.
obj , ok := in . ( Object )
if ! ok {
return fmt . Errorf ( "unable to convert object type %T to Unstructured, must be a runtime.Object" , in )
}
gvks , unversioned , err := s . ObjectKinds ( obj )
if err != nil {
return err
}
gvk := gvks [ 0 ]
// if no conversion is necessary, convert immediately
if unversioned || gvk . Version != APIVersionInternal {
content , err := DefaultUnstructuredConverter . ToUnstructured ( in )
if err != nil {
return err
}
unstructuredOut . SetUnstructuredContent ( content )
unstructuredOut . GetObjectKind ( ) . SetGroupVersionKind ( gvk )
return nil
}
// attempt to convert the object to an external version first.
target , ok := context . ( GroupVersioner )
if ! ok {
return fmt . Errorf ( "unable to convert the internal object type %T to Unstructured without providing a preferred version to convert to" , in )
}
// Convert is implicitly unsafe, so we don't need to perform a safe conversion
versioned , err := s . UnsafeConvertToVersion ( obj , target )
if err != nil {
return err
}
content , err := DefaultUnstructuredConverter . ToUnstructured ( versioned )
if err != nil {
return err
}
unstructuredOut . SetUnstructuredContent ( content )
return nil
case okIn :
// converting an unstructured object to any type is modeled by first converting
// the input to a versioned type, then running standard conversions
typed , err := s . unstructuredToTyped ( unstructuredIn )
if err != nil {
return err
}
in = typed
}
2017-11-20 09:30:52 -05:00
flags , meta := s . generateConvertMeta ( in )
meta . Context = context
if flags == 0 {
flags = conversion . AllowDifferentFieldTypeNames
}
return s . converter . Convert ( in , out , flags , meta )
}
2018-07-25 04:17:02 -04:00
// ConvertFieldLabel alters the given field label and value for an kind field selector from
// versioned representation to an unversioned one or returns an error.
2019-03-25 08:03:02 -04:00
func ( s * Scheme ) ConvertFieldLabel ( gvk schema . GroupVersionKind , label , value string ) ( string , string , error ) {
conversionFunc , ok := s . fieldLabelConversionFuncs [ gvk ]
2017-11-20 09:30:52 -05:00
if ! ok {
return DefaultMetaV1FieldSelectorConversion ( label , value )
}
return conversionFunc ( label , value )
}
// ConvertToVersion attempts to convert an input object to its matching Kind in another
// version within this scheme. Will return an error if the provided version does not
// contain the inKind (or a mapping by name defined with AddKnownTypeWithName). Will also
// return an error if the conversion does not result in a valid Object being
// returned. Passes target down to the conversion methods as the Context on the scope.
func ( s * Scheme ) ConvertToVersion ( in Object , target GroupVersioner ) ( Object , error ) {
return s . convertToVersion ( true , in , target )
}
// UnsafeConvertToVersion will convert in to the provided target if such a conversion is possible,
// but does not guarantee the output object does not share fields with the input object. It attempts to be as
// efficient as possible when doing conversion.
func ( s * Scheme ) UnsafeConvertToVersion ( in Object , target GroupVersioner ) ( Object , error ) {
return s . convertToVersion ( false , in , target )
}
// convertToVersion handles conversion with an optional copy.
func ( s * Scheme ) convertToVersion ( copy bool , in Object , target GroupVersioner ) ( Object , error ) {
2018-07-25 04:17:02 -04:00
var t reflect . Type
if u , ok := in . ( Unstructured ) ; ok {
typed , err := s . unstructuredToTyped ( u )
if err != nil {
return nil , err
}
in = typed
// unstructuredToTyped returns an Object, which must be a pointer to a struct.
t = reflect . TypeOf ( in ) . Elem ( )
} else {
// determine the incoming kinds with as few allocations as possible.
t = reflect . TypeOf ( in )
if t . Kind ( ) != reflect . Ptr {
return nil , fmt . Errorf ( "only pointer types may be converted: %v" , t )
}
t = t . Elem ( )
if t . Kind ( ) != reflect . Struct {
return nil , fmt . Errorf ( "only pointers to struct types may be converted: %v" , t )
}
2017-11-20 09:30:52 -05:00
}
2018-07-25 04:17:02 -04:00
2017-11-20 09:30:52 -05:00
kinds , ok := s . typeToGVK [ t ]
if ! ok || len ( kinds ) == 0 {
2019-03-25 08:03:02 -04:00
return nil , NewNotRegisteredErrForType ( s . schemeName , t )
2017-11-20 09:30:52 -05:00
}
gvk , ok := target . KindForGroupVersionKinds ( kinds )
if ! ok {
// try to see if this type is listed as unversioned (for legacy support)
// TODO: when we move to server API versions, we should completely remove the unversioned concept
if unversionedKind , ok := s . unversionedTypes [ t ] ; ok {
if gvk , ok := target . KindForGroupVersionKinds ( [ ] schema . GroupVersionKind { unversionedKind } ) ; ok {
2018-07-25 04:17:02 -04:00
return copyAndSetTargetKind ( copy , in , gvk )
2017-11-20 09:30:52 -05:00
}
2018-07-25 04:17:02 -04:00
return copyAndSetTargetKind ( copy , in , unversionedKind )
2017-11-20 09:30:52 -05:00
}
2019-03-25 08:03:02 -04:00
return nil , NewNotRegisteredErrForTarget ( s . schemeName , t , target )
2017-11-20 09:30:52 -05:00
}
// target wants to use the existing type, set kind and return (no conversion necessary)
for _ , kind := range kinds {
if gvk == kind {
2018-07-25 04:17:02 -04:00
return copyAndSetTargetKind ( copy , in , gvk )
2017-11-20 09:30:52 -05:00
}
}
// type is unversioned, no conversion necessary
if unversionedKind , ok := s . unversionedTypes [ t ] ; ok {
if gvk , ok := target . KindForGroupVersionKinds ( [ ] schema . GroupVersionKind { unversionedKind } ) ; ok {
2018-07-25 04:17:02 -04:00
return copyAndSetTargetKind ( copy , in , gvk )
2017-11-20 09:30:52 -05:00
}
2018-07-25 04:17:02 -04:00
return copyAndSetTargetKind ( copy , in , unversionedKind )
2017-11-20 09:30:52 -05:00
}
out , err := s . New ( gvk )
if err != nil {
return nil , err
}
if copy {
in = in . DeepCopyObject ( )
}
flags , meta := s . generateConvertMeta ( in )
meta . Context = target
if err := s . converter . Convert ( in , out , flags , meta ) ; err != nil {
return nil , err
}
setTargetKind ( out , gvk )
return out , nil
}
2018-07-25 04:17:02 -04:00
// unstructuredToTyped attempts to transform an unstructured object to a typed
// object if possible. It will return an error if conversion is not possible, or the versioned
// Go form of the object. Note that this conversion will lose fields.
func ( s * Scheme ) unstructuredToTyped ( in Unstructured ) ( Object , error ) {
// the type must be something we recognize
gvks , _ , err := s . ObjectKinds ( in )
if err != nil {
return nil , err
}
typed , err := s . New ( gvks [ 0 ] )
if err != nil {
return nil , err
}
if err := DefaultUnstructuredConverter . FromUnstructured ( in . UnstructuredContent ( ) , typed ) ; err != nil {
return nil , fmt . Errorf ( "unable to convert unstructured object to %v: %v" , gvks [ 0 ] , err )
}
return typed , nil
}
2017-11-20 09:30:52 -05:00
// generateConvertMeta constructs the meta value we pass to Convert.
func ( s * Scheme ) generateConvertMeta ( in interface { } ) ( conversion . FieldMatchingFlags , * conversion . Meta ) {
return s . converter . DefaultMeta ( reflect . TypeOf ( in ) )
}
// copyAndSetTargetKind performs a conditional copy before returning the object, or an error if copy was not successful.
2018-07-25 04:17:02 -04:00
func copyAndSetTargetKind ( copy bool , obj Object , kind schema . GroupVersionKind ) ( Object , error ) {
2017-11-20 09:30:52 -05:00
if copy {
obj = obj . DeepCopyObject ( )
}
setTargetKind ( obj , kind )
return obj , nil
}
// setTargetKind sets the kind on an object, taking into account whether the target kind is the internal version.
func setTargetKind ( obj Object , kind schema . GroupVersionKind ) {
if kind . Version == APIVersionInternal {
// internal is a special case
// TODO: look at removing the need to special case this
obj . GetObjectKind ( ) . SetGroupVersionKind ( schema . GroupVersionKind { } )
return
}
obj . GetObjectKind ( ) . SetGroupVersionKind ( kind )
}
2018-07-25 04:17:02 -04:00
// SetVersionPriority allows specifying a precise order of priority. All specified versions must be in the same group,
// and the specified order overwrites any previously specified order for this group
func ( s * Scheme ) SetVersionPriority ( versions ... schema . GroupVersion ) error {
groups := sets . String { }
order := [ ] string { }
for _ , version := range versions {
if len ( version . Version ) == 0 || version . Version == APIVersionInternal {
return fmt . Errorf ( "internal versions cannot be prioritized: %v" , version )
}
groups . Insert ( version . Group )
order = append ( order , version . Version )
}
if len ( groups ) != 1 {
return fmt . Errorf ( "must register versions for exactly one group: %v" , strings . Join ( groups . List ( ) , ", " ) )
}
s . versionPriority [ groups . List ( ) [ 0 ] ] = order
return nil
}
// PrioritizedVersionsForGroup returns versions for a single group in priority order
func ( s * Scheme ) PrioritizedVersionsForGroup ( group string ) [ ] schema . GroupVersion {
ret := [ ] schema . GroupVersion { }
for _ , version := range s . versionPriority [ group ] {
ret = append ( ret , schema . GroupVersion { Group : group , Version : version } )
}
for _ , observedVersion := range s . observedVersions {
if observedVersion . Group != group {
continue
}
found := false
for _ , existing := range ret {
if existing == observedVersion {
found = true
break
}
}
if ! found {
ret = append ( ret , observedVersion )
}
}
return ret
}
// PrioritizedVersionsAllGroups returns all known versions in their priority order. Groups are random, but
// versions for a single group are prioritized
func ( s * Scheme ) PrioritizedVersionsAllGroups ( ) [ ] schema . GroupVersion {
ret := [ ] schema . GroupVersion { }
for group , versions := range s . versionPriority {
for _ , version := range versions {
ret = append ( ret , schema . GroupVersion { Group : group , Version : version } )
}
}
for _ , observedVersion := range s . observedVersions {
found := false
for _ , existing := range ret {
if existing == observedVersion {
found = true
break
}
}
if ! found {
ret = append ( ret , observedVersion )
}
}
return ret
}
// PreferredVersionAllGroups returns the most preferred version for every group.
// group ordering is random.
func ( s * Scheme ) PreferredVersionAllGroups ( ) [ ] schema . GroupVersion {
ret := [ ] schema . GroupVersion { }
for group , versions := range s . versionPriority {
for _ , version := range versions {
ret = append ( ret , schema . GroupVersion { Group : group , Version : version } )
break
}
}
for _ , observedVersion := range s . observedVersions {
found := false
for _ , existing := range ret {
if existing . Group == observedVersion . Group {
found = true
break
}
}
if ! found {
ret = append ( ret , observedVersion )
}
}
return ret
}
// IsGroupRegistered returns true if types for the group have been registered with the scheme
func ( s * Scheme ) IsGroupRegistered ( group string ) bool {
for _ , observedVersion := range s . observedVersions {
if observedVersion . Group == group {
return true
}
}
return false
}
// IsVersionRegistered returns true if types for the version have been registered with the scheme
func ( s * Scheme ) IsVersionRegistered ( version schema . GroupVersion ) bool {
for _ , observedVersion := range s . observedVersions {
if observedVersion == version {
return true
}
}
return false
}
func ( s * Scheme ) addObservedVersion ( version schema . GroupVersion ) {
if len ( version . Version ) == 0 || version . Version == APIVersionInternal {
return
}
for _ , observedVersion := range s . observedVersions {
if observedVersion == version {
return
}
}
s . observedVersions = append ( s . observedVersions , version )
}
2019-03-25 08:03:02 -04:00
func ( s * Scheme ) Name ( ) string {
return s . schemeName
}
// internalPackages are packages that ignored when creating a default reflector name. These packages are in the common
// call chains to NewReflector, so they'd be low entropy names for reflectors
var internalPackages = [ ] string { "k8s.io/apimachinery/pkg/runtime/scheme.go" }