// ServerGroups returns the supported groups, with information like supported versions and the // preferred version. func (d *DiscoveryClient) ServerGroups() (apiGroupList *unversioned.APIGroupList, err error) { // Get the groupVersions exposed at /api v := &unversioned.APIVersions{} err = d.restClient.Get().AbsPath(d.LegacyPrefix).Do().Into(v) apiGroup := unversioned.APIGroup{} if err == nil { apiGroup = apiVersionsToAPIGroup(v) } if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) { return nil, err } // Get the groupVersions exposed at /apis apiGroupList = &unversioned.APIGroupList{} err = d.restClient.Get().AbsPath("/apis").Do().Into(apiGroupList) if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) { return nil, err } // to be compatible with a v1.0 server, if it's a 403 or 404, ignore and return whatever we got from /api if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) { apiGroupList = &unversioned.APIGroupList{} } // append the group retrieved from /api to the list apiGroupList.Groups = append(apiGroupList.Groups, apiGroup) return apiGroupList, nil }
// GetAPIGroupResources uses the provided discovery client to gather // discovery information and populate a slice of APIGroupResources. func GetAPIGroupResources(cl DiscoveryInterface) ([]*APIGroupResources, error) { apiGroups, err := cl.ServerGroups() if err != nil { return nil, err } var result []*APIGroupResources for _, group := range apiGroups.Groups { groupResources := &APIGroupResources{ Group: group, VersionedResources: make(map[string][]unversioned.APIResource), } for _, version := range group.Versions { resources, err := cl.ServerResourcesForGroupVersion(version.GroupVersion) if err != nil { if errors.IsNotFound(err) { continue // ignore as this can race with deletion of 3rd party APIs } return nil, err } groupResources.VersionedResources[version.Version] = resources.APIResources } result = append(result, groupResources) } return result, nil }
// ServerResourcesForGroupVersion returns the supported resources for a group and version. func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (resources *unversioned.APIResourceList, err error) { url := url.URL{} if len(groupVersion) == 0 { return nil, fmt.Errorf("groupVersion shouldn't be empty") } if len(d.LegacyPrefix) > 0 && groupVersion == "v1" { url.Path = d.LegacyPrefix + "/" + groupVersion } else { url.Path = "/apis/" + groupVersion } resources = &unversioned.APIResourceList{} err = d.restClient.Get().AbsPath(url.String()).Do().Into(resources) if err != nil { // ignore 403 or 404 error to be compatible with an v1.0 server. if groupVersion == "v1" && (errors.IsNotFound(err) || errors.IsForbidden(err)) { return resources, nil } return nil, err } return resources, nil }
func TestStoreToPodLister(t *testing.T) { // We test with and without a namespace index, because StoreToPodLister has // special logic to work on namespaces even when no namespace index is // present. stores := []Indexer{ NewIndexer(MetaNamespaceKeyFunc, Indexers{NamespaceIndex: MetaNamespaceIndexFunc}), NewIndexer(MetaNamespaceKeyFunc, Indexers{}), } for _, store := range stores { ids := []string{"foo", "bar", "baz"} for _, id := range ids { store.Add(&v1.Pod{ ObjectMeta: v1.ObjectMeta{ Namespace: "other", Name: id, Labels: map[string]string{"name": id}, }, }) } store.Add(&v1.Pod{ ObjectMeta: v1.ObjectMeta{ Name: "quux", Namespace: v1.NamespaceDefault, Labels: map[string]string{"name": "quux"}, }, }) spl := StoreToPodLister{store} // Verify that we can always look up by Namespace. defaultPods, err := spl.Pods(v1.NamespaceDefault).List(labels.Set{}.AsSelectorPreValidated()) if err != nil { t.Errorf("Unexpected error: %v", err) } else if e, a := 1, len(defaultPods); e != a { t.Errorf("Expected %v, got %v", e, a) } else if e, a := "quux", defaultPods[0].Name; e != a { t.Errorf("Expected %v, got %v", e, a) } for _, id := range ids { got, err := spl.List(labels.Set{"name": id}.AsSelectorPreValidated()) if err != nil { t.Errorf("Unexpected error: %v", err) continue } if e, a := 1, len(got); e != a { t.Errorf("Expected %v, got %v", e, a) continue } if e, a := id, got[0].Name; e != a { t.Errorf("Expected %v, got %v", e, a) continue } _, err = spl.Pods("other").Get(id) if err != nil { t.Errorf("unexpected error: %v", err) } } if _, err := spl.Pods("").Get("qux"); !apierrors.IsNotFound(err) { t.Error("Unexpected pod exists") } } }
func TestTransformResponseNegotiate(t *testing.T) { invalid := []byte("aaaaa") uri, _ := url.Parse("http://localhost") testCases := []struct { Response *http.Response Data []byte Created bool Error bool ErrFn func(err error) bool ContentType string Called bool ExpectContentType string Decoder runtime.Decoder NegotiateErr error }{ { ContentType: "application/json", Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/json"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { ContentType: "application/json", Response: &http.Response{ StatusCode: 401, Header: http.Header{"Content-Type": []string{"application/protobuf"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), Called: true, ExpectContentType: "application/protobuf", Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsUnauthorized(err) }, }, { ContentType: "application/json", Response: &http.Response{ StatusCode: 500, Header: http.Header{"Content-Type": []string{"application/,others"}}, }, Decoder: testapi.Default.Codec(), Error: true, ErrFn: func(err error) bool { return err.Error() == "Internal error occurred: mime: expected token after slash" && err.(apierrors.APIStatus).Status().Code == 500 }, }, { // no negotiation when no content type specified Response: &http.Response{ StatusCode: 200, Header: http.Header{"Content-Type": []string{"text/any"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), }, { // no negotiation when no response content type specified ContentType: "text/any", Response: &http.Response{ StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), }, { // unrecognized content type is not handled ContentType: "application/json", Response: &http.Response{ StatusCode: 404, Header: http.Header{"Content-Type": []string{"application/unrecognized"}}, Body: ioutil.NopCloser(bytes.NewReader(invalid)), }, Decoder: testapi.Default.Codec(), NegotiateErr: fmt.Errorf("aaaa"), Called: true, ExpectContentType: "application/unrecognized", Error: true, ErrFn: func(err error) bool { return err.Error() != "aaaaa" && apierrors.IsNotFound(err) }, }, } for i, test := range testCases { serializers := defaultSerializers() negotiator := &renegotiator{ decoder: test.Decoder, err: test.NegotiateErr, } serializers.RenegotiatedDecoder = negotiator.invoke contentConfig := defaultContentConfig() contentConfig.ContentType = test.ContentType r := NewRequest(nil, "", uri, "", contentConfig, serializers, nil, nil) if test.Response.Body == nil { test.Response.Body = ioutil.NopCloser(bytes.NewReader([]byte{})) } result := r.transformResponse(test.Response, &http.Request{}) _, err := result.body, result.err hasErr := err != nil if hasErr != test.Error { t.Errorf("%d: unexpected error: %t %v", i, test.Error, err) continue } else if hasErr && test.Response.StatusCode > 399 { status, ok := err.(apierrors.APIStatus) if !ok { t.Errorf("%d: response should have been transformable into APIStatus: %v", i, err) continue } if int(status.Status().Code) != test.Response.StatusCode { t.Errorf("%d: status code did not match response: %#v", i, status.Status()) } } if test.ErrFn != nil && !test.ErrFn(err) { t.Errorf("%d: error function did not match: %v", i, err) } if negotiator.called != test.Called { t.Errorf("%d: negotiator called %t != %t", i, negotiator.called, test.Called) } if !test.Called { continue } if negotiator.contentType != test.ExpectContentType { t.Errorf("%d: unexpected content type: %s", i, negotiator.contentType) } } }