func (c *RESTClient) request(verb string) *rest.Request { config := rest.ContentConfig{ ContentType: runtime.ContentTypeJSON, GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion, NegotiatedSerializer: c.NegotiatedSerializer, } groupName := api.GroupName if c.GroupName != "" { groupName = c.GroupName } ns := c.NegotiatedSerializer info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON) internalVersion := unversioned.GroupVersion{ Group: registered.GroupOrDie(groupName).GroupVersion.Group, Version: runtime.APIVersionInternal, } internalVersion.Version = runtime.APIVersionInternal serializers := rest.Serializers{ Encoder: ns.EncoderForVersion(info.Serializer, registered.GroupOrDie(api.GroupName).GroupVersion), Decoder: ns.DecoderToVersion(info.Serializer, internalVersion), } if info.StreamSerializer != nil { serializers.StreamingSerializer = info.StreamSerializer.Serializer serializers.Framer = info.StreamSerializer.Framer } return rest.NewRequest(c, verb, &url.URL{Host: "localhost"}, "", config, serializers, nil, nil) }
// SwaggerSchema retrieves and parses the swagger API schema the server supports. func (d *DiscoveryClient) SwaggerSchema(version unversioned.GroupVersion) (*swagger.ApiDeclaration, error) { if version.Empty() { return nil, fmt.Errorf("groupVersion cannot be empty") } groupList, err := d.ServerGroups() if err != nil { return nil, err } groupVersions := unversioned.ExtractGroupVersions(groupList) // This check also takes care the case that kubectl is newer than the running endpoint if stringDoesntExistIn(version.String(), groupVersions) { return nil, fmt.Errorf("API version: %v is not supported by the server. Use one of: %v", version, groupVersions) } var path string if len(d.LegacyPrefix) > 0 && version == v1.SchemeGroupVersion { path = "/swaggerapi" + d.LegacyPrefix + "/" + version.Version } else { path = "/swaggerapi/apis/" + version.Group + "/" + version.Version } body, err := d.restClient.Get().AbsPath(path).Do().Raw() if err != nil { return nil, err } var schema swagger.ApiDeclaration err = json.Unmarshal(body, &schema) if err != nil { return nil, fmt.Errorf("got '%s': %v", string(body), err) } return &schema, nil }
// NegotiateVersion queries the server's supported api versions to find // a version that both client and server support. // - If no version is provided, try registered client versions in order of // preference. // - If version is provided and the server does not support it, // return an error. func NegotiateVersion(client DiscoveryInterface, requiredGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) { clientVersions := sets.String{} for _, gv := range clientRegisteredGVs { clientVersions.Insert(gv.String()) } groups, err := client.ServerGroups() if err != nil { // This is almost always a connection error, and higher level code should treat this as a generic error, // not a negotiation specific error. return nil, err } versions := unversioned.ExtractGroupVersions(groups) serverVersions := sets.String{} for _, v := range versions { serverVersions.Insert(v) } // If version explicitly requested verify that both client and server support it. // If server does not support warn, but try to negotiate a lower version. if requiredGV != nil { if !clientVersions.Has(requiredGV.String()) { return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", requiredGV, clientVersions) } // If the server supports no versions, then we should just use the preferredGV // This can happen because discovery fails due to 403 Forbidden errors if len(serverVersions) == 0 { return requiredGV, nil } if serverVersions.Has(requiredGV.String()) { return requiredGV, nil } // If we are using an explicit config version the server does not support, fail. return nil, fmt.Errorf("server does not support API version %q", requiredGV) } for _, clientGV := range clientRegisteredGVs { if serverVersions.Has(clientGV.String()) { // Version was not explicitly requested in command config (--api-version). // Ok to fall back to a supported version with a warning. // TODO: caesarxuchao: enable the warning message when we have // proper fix. Please refer to issue #14895. // if len(version) != 0 { // glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion) // } t := clientGV return &t, nil } } // if we have no server versions and we have no required version, choose the first clientRegisteredVersion if len(serverVersions) == 0 && len(clientRegisteredGVs) > 0 { return &clientRegisteredGVs[0], nil } return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v", serverVersions, clientVersions) }
// 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 unversioned.GroupVersion, types ...Object) { s.AddKnownTypes(version, types...) for _, obj := range types { t := reflect.TypeOf(obj).Elem() gvk := version.WithKind(t.Name()) s.unversionedTypes[t] = gvk if _, ok := s.unversionedKinds[gvk.Kind]; ok { panic(fmt.Sprintf("%v has already been registered as unversioned kind %q - kind name must be unique", reflect.TypeOf(t), gvk.Kind)) } s.unversionedKinds[gvk.Kind] = t } }
// AddToGroupVersion registers the watch external and internal kinds with the scheme, and ensures the proper // conversions are in place. func AddToGroupVersion(scheme *runtime.Scheme, groupVersion unversioned.GroupVersion) { scheme.AddKnownTypeWithName(groupVersion.WithKind(WatchEventKind), &Event{}) scheme.AddKnownTypeWithName( unversioned.GroupVersion{Group: groupVersion.Group, Version: runtime.APIVersionInternal}.WithKind(WatchEventKind), &InternalEvent{}, ) scheme.AddConversionFuncs( Convert_versioned_Event_to_watch_Event, Convert_versioned_InternalEvent_to_versioned_Event, Convert_watch_Event_to_versioned_Event, Convert_versioned_Event_to_versioned_InternalEvent, ) }
// NewUnstructuredObjectTyper returns a runtime.ObjectTyper for // unstructred objects based on discovery information. func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *UnstructuredObjectTyper { dot := &UnstructuredObjectTyper{registered: make(map[unversioned.GroupVersionKind]bool)} for _, group := range groupResources { for _, discoveryVersion := range group.Group.Versions { resources, ok := group.VersionedResources[discoveryVersion.Version] if !ok { continue } gv := unversioned.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version} for _, resource := range resources { dot.registered[gv.WithKind(resource.Kind)] = true } } } return dot }
// 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 unversioned.GroupVersion, types ...Object) { if len(gv.Version) == 0 { panic(fmt.Sprintf("version is required on all types: %s %v", gv, types[0])) } for _, obj := range types { t := reflect.TypeOf(obj) 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.") } gvk := gv.WithKind(t.Name()) s.gvkToType[gvk] = t s.typeToGVK[t] = append(s.typeToGVK[t], gvk) } }
// serverPreferredResources returns the supported resources with the version preferred by the // server. If namespaced is true, only namespaced resources will be returned. func (d *DiscoveryClient) serverPreferredResources(namespaced bool) ([]unversioned.GroupVersionResource, error) { // retry in case the groups supported by the server change after ServerGroup() returns. const maxRetries = 2 var failedGroups map[unversioned.GroupVersion]error var results []unversioned.GroupVersionResource RetrieveGroups: for i := 0; i < maxRetries; i++ { results = []unversioned.GroupVersionResource{} failedGroups = make(map[unversioned.GroupVersion]error) serverGroupList, err := d.ServerGroups() if err != nil { return results, err } for _, apiGroup := range serverGroupList.Groups { preferredVersion := apiGroup.PreferredVersion groupVersion := unversioned.GroupVersion{Group: apiGroup.Name, Version: preferredVersion.Version} apiResourceList, err := d.ServerResourcesForGroupVersion(preferredVersion.GroupVersion) if err != nil { if i < maxRetries-1 { continue RetrieveGroups } failedGroups[groupVersion] = err continue } for _, apiResource := range apiResourceList.APIResources { // ignore the root scoped resources if "namespaced" is true. if namespaced && !apiResource.Namespaced { continue } if strings.Contains(apiResource.Name, "/") { continue } results = append(results, groupVersion.WithResource(apiResource.Name)) } } if len(failedGroups) == 0 { return results, nil } } return results, &ErrGroupDiscoveryFailed{Groups: failedGroups} }
// DecodeParameters converts the provided url.Values into an object of type From with the kind of into, and then // converts that object to into (if necessary). Returns an error if the operation cannot be completed. func (c *parameterCodec) DecodeParameters(parameters url.Values, from unversioned.GroupVersion, into Object) error { if len(parameters) == 0 { return nil } targetGVKs, _, err := c.typer.ObjectKinds(into) if err != nil { return err } targetGVK := targetGVKs[0] if targetGVK.GroupVersion() == from { return c.convertor.Convert(¶meters, into, nil) } input, err := c.creator.New(from.WithKind(targetGVK.Kind)) if err != nil { return err } if err := c.convertor.Convert(¶meters, input, nil); err != nil { return err } return c.convertor.Convert(input, into, nil) }
// NewRESTMapper returns a PriorityRESTMapper based on the discovered // groups and resourced passed in. func NewRESTMapper(groupResources []*APIGroupResources, versionInterfaces meta.VersionInterfacesFunc) meta.RESTMapper { unionMapper := meta.MultiRESTMapper{} var groupPriority []string var resourcePriority []unversioned.GroupVersionResource var kindPriority []unversioned.GroupVersionKind for _, group := range groupResources { groupPriority = append(groupPriority, group.Group.Name) if len(group.Group.PreferredVersion.Version) != 0 { preffered := group.Group.PreferredVersion.Version if _, ok := group.VersionedResources[preffered]; ok { resourcePriority = append(resourcePriority, unversioned.GroupVersionResource{ Group: group.Group.Name, Version: group.Group.PreferredVersion.Version, Resource: meta.AnyResource, }) kindPriority = append(kindPriority, unversioned.GroupVersionKind{ Group: group.Group.Name, Version: group.Group.PreferredVersion.Version, Kind: meta.AnyKind, }) } } for _, discoveryVersion := range group.Group.Versions { resources, ok := group.VersionedResources[discoveryVersion.Version] if !ok { continue } gv := unversioned.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version} versionMapper := meta.NewDefaultRESTMapper([]unversioned.GroupVersion{gv}, versionInterfaces) for _, resource := range resources { scope := meta.RESTScopeNamespace if !resource.Namespaced { scope = meta.RESTScopeRoot } versionMapper.Add(gv.WithKind(resource.Kind), scope) // TODO only do this if it supports listing versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope) } // TODO why is this type not in discovery (at least for "v1") versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot) unionMapper = append(unionMapper, versionMapper) } } for _, group := range groupPriority { resourcePriority = append(resourcePriority, unversioned.GroupVersionResource{ Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource, }) kindPriority = append(kindPriority, unversioned.GroupVersionKind{ Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind, }) } return meta.PriorityRESTMapper{ Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority, } }
// NegotiateVersion queries the server's supported api versions to find // a version that both client and server support. // - If no version is provided, try registered client versions in order of // preference. // - If version is provided, but not default config (explicitly requested via // commandline flag), and is unsupported by the server, print a warning to // stderr and try client's registered versions in order of preference. // - If version is config default, and the server does not support it, // return an error. func NegotiateVersion(client DiscoveryInterface, c *rest.Config, requestedGV *unversioned.GroupVersion, clientRegisteredGVs []unversioned.GroupVersion) (*unversioned.GroupVersion, error) { var err error if client == nil { client, err = NewDiscoveryClientForConfig(c) if err != nil { return nil, err } } clientVersions := sets.String{} for _, gv := range clientRegisteredGVs { clientVersions.Insert(gv.String()) } groups, err := client.ServerGroups() if err != nil { // This is almost always a connection error, and higher level code should treat this as a generic error, // not a negotiation specific error. return nil, err } versions := unversioned.ExtractGroupVersions(groups) serverVersions := sets.String{} for _, v := range versions { serverVersions.Insert(v) } // If no version requested, use config version (may also be empty). // make a copy of the original so we don't risk mutating input here or in the returned value var preferredGV *unversioned.GroupVersion switch { case requestedGV != nil: t := *requestedGV preferredGV = &t case c.GroupVersion != nil: t := *c.GroupVersion preferredGV = &t } // If version explicitly requested verify that both client and server support it. // If server does not support warn, but try to negotiate a lower version. if preferredGV != nil { if !clientVersions.Has(preferredGV.String()) { return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", preferredGV, clientVersions) } // If the server supports no versions, then we should just use the preferredGV // This can happen because discovery fails due to 403 Forbidden errors if len(serverVersions) == 0 { return preferredGV, nil } if serverVersions.Has(preferredGV.String()) { return preferredGV, nil } // If we are using an explicit config version the server does not support, fail. if (c.GroupVersion != nil) && (*preferredGV == *c.GroupVersion) { return nil, fmt.Errorf("server does not support API version %q", preferredGV) } } for _, clientGV := range clientRegisteredGVs { if serverVersions.Has(clientGV.String()) { // Version was not explicitly requested in command config (--api-version). // Ok to fall back to a supported version with a warning. // TODO: caesarxuchao: enable the warning message when we have // proper fix. Please refer to issue #14895. // if len(version) != 0 { // glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion) // } t := clientGV return &t, nil } } return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v", serverVersions, clientVersions) }