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 := schema.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 schema.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 := metav1.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 }
// 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) ([]schema.GroupVersionResource, error) { // retry in case the groups supported by the server change after ServerGroup() returns. const maxRetries = 2 var failedGroups map[schema.GroupVersion]error var results []schema.GroupVersionResource var resources map[schema.GroupResource]string RetrieveGroups: for i := 0; i < maxRetries; i++ { results = []schema.GroupVersionResource{} resources = map[schema.GroupResource]string{} failedGroups = make(map[schema.GroupVersion]error) serverGroupList, err := d.ServerGroups() if err != nil { return results, err } for _, apiGroup := range serverGroupList.Groups { versions := apiGroup.Versions for _, version := range versions { groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version} apiResourceList, err := d.ServerResourcesForGroupVersion(version.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 } gvr := groupVersion.WithResource(apiResource.Name) if _, ok := resources[gvr.GroupResource()]; ok { if gvr.Version != apiGroup.PreferredVersion.Version { continue } // remove previous entry, because it will be replaced with a preferred one for i := range results { if results[i].GroupResource() == gvr.GroupResource() { results = append(results[:i], results[i+1:]...) } } } resources[gvr.GroupResource()] = gvr.Version results = append(results, gvr) } } } if len(failedGroups) == 0 { return results, nil } } return results, &ErrGroupDiscoveryFailed{Groups: failedGroups} }
// 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 *schema.GroupVersion, clientRegisteredGVs []schema.GroupVersion) (*schema.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 := metav1.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) }
func (m PriorityRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (mapping *RESTMapping, err error) { mappings, err := m.Delegate.RESTMappings(gk) if err != nil { return nil, err } // any versions the user provides take priority priorities := m.KindPriority if len(versions) > 0 { priorities = make([]schema.GroupVersionKind, 0, len(m.KindPriority)+len(versions)) for _, version := range versions { gv := schema.GroupVersion{ Version: version, Group: gk.Group, } priorities = append(priorities, gv.WithKind(AnyKind)) } priorities = append(priorities, m.KindPriority...) } remaining := append([]*RESTMapping{}, mappings...) for _, pattern := range priorities { var matching []*RESTMapping for _, m := range remaining { if kindMatches(pattern, m.GroupVersionKind) { matching = append(matching, m) } } switch len(matching) { case 0: // if you have no matches, then nothing matched this pattern just move to the next continue case 1: // one match, return return matching[0], nil default: // more than one match, use the matched hits as the list moving to the next pattern. // this way you can have a series of selection criteria remaining = matching } } if len(remaining) == 1 { return remaining[0], nil } var kinds []schema.GroupVersionKind for _, m := range mappings { kinds = append(kinds, m.GroupVersionKind) } return nil, &AmbiguousKindError{PartialKind: gk.WithVersion(""), MatchingKinds: kinds} }
// 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 schema.GroupVersion) { scheme.AddKnownTypeWithName(groupVersion.WithKind(WatchEventKind), &Event{}) scheme.AddKnownTypeWithName( schema.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[schema.GroupVersionKind]bool)} for _, group := range groupResources { for _, discoveryVersion := range group.Group.Versions { resources, ok := group.VersionedResources[discoveryVersion.Version] if !ok { continue } gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version} for _, resource := range resources { dot.registered[gv.WithKind(resource.Kind)] = true } } } return dot }
// 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 schema.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 resources passed in. func NewRESTMapper(groupResources []*APIGroupResources, versionInterfaces meta.VersionInterfacesFunc) meta.RESTMapper { unionMapper := meta.MultiRESTMapper{} var groupPriority []string var resourcePriority []schema.GroupVersionResource var kindPriority []schema.GroupVersionKind for _, group := range groupResources { groupPriority = append(groupPriority, group.Group.Name) if len(group.Group.PreferredVersion.Version) != 0 { preferred := group.Group.PreferredVersion.Version if _, ok := group.VersionedResources[preferred]; ok { resourcePriority = append(resourcePriority, schema.GroupVersionResource{ Group: group.Group.Name, Version: group.Group.PreferredVersion.Version, Resource: meta.AnyResource, }) kindPriority = append(kindPriority, schema.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 := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version} versionMapper := meta.NewDefaultRESTMapper([]schema.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, schema.GroupVersionResource{ Group: group, Version: meta.AnyVersion, Resource: meta.AnyResource, }) kindPriority = append(kindPriority, schema.GroupVersionKind{ Group: group, Version: meta.AnyVersion, Kind: meta.AnyKind, }) } return meta.PriorityRESTMapper{ Delegate: unionMapper, ResourcePriority: resourcePriority, KindPriority: kindPriority, } }