diff --git a/pkg/client/unversioned/client_test.go b/pkg/client/unversioned/client_test.go index 3b375cf161d..d9eefd53c0f 100644 --- a/pkg/client/unversioned/client_test.go +++ b/pkg/client/unversioned/client_test.go @@ -271,6 +271,76 @@ func TestGetServerVersion(t *testing.T) { } } +func TestGetServerGroupsWithV1Server(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + var obj interface{} + switch req.URL.Path { + case "/api": + obj = &unversioned.APIVersions{ + Versions: []string{ + "v1", + }, + } + default: + w.WriteHeader(http.StatusNotFound) + return + } + output, err := json.Marshal(obj) + if err != nil { + t.Errorf("unexpected encoding error: %v", err) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(output) + })) + client := NewOrDie(&Config{Host: server.URL}) + // ServerGroups should not return an error even if server returns error at /api and /apis + apiGroupList, err := client.Discovery().ServerGroups() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + groupVersions := ExtractGroupVersions(apiGroupList) + if !reflect.DeepEqual(groupVersions, []string{"v1"}) { + t.Errorf("expected: %q, got: %q", []string{"v1"}, groupVersions) + } +} + +func TestGetServerResourcesWithV1Server(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + var obj interface{} + switch req.URL.Path { + case "/api": + obj = &unversioned.APIVersions{ + Versions: []string{ + "v1", + }, + } + default: + w.WriteHeader(http.StatusNotFound) + return + } + output, err := json.Marshal(obj) + if err != nil { + t.Errorf("unexpected encoding error: %v", err) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(output) + })) + client := NewOrDie(&Config{Host: server.URL}) + // ServerResources should not return an error even if server returns error at /api/v1. + resourceMap, err := client.Discovery().ServerResources() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + if _, found := resourceMap["v1"]; !found { + t.Errorf("missing v1 in resource map") + } + +} + func TestGetServerResources(t *testing.T) { stable := unversioned.APIResourceList{ GroupVersion: "v1", diff --git a/pkg/client/unversioned/discovery_client.go b/pkg/client/unversioned/discovery_client.go index 362e6e63e2f..cc3eb6665e3 100644 --- a/pkg/client/unversioned/discovery_client.go +++ b/pkg/client/unversioned/discovery_client.go @@ -17,11 +17,10 @@ limitations under the License. package unversioned import ( - "encoding/json" - "fmt" - "net/http" "net/url" + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/unversioned" ) @@ -50,8 +49,7 @@ type ServerResourcesInterface interface { // DiscoveryClient implements the functions that dicovery server-supported API groups, // versions and resources. type DiscoveryClient struct { - httpClient HTTPClient - baseURL url.URL + *RESTClient } // Convert unversioned.APIVersions to unversioned.APIGroup. APIVersions is used by legacy v1, so @@ -71,42 +69,29 @@ func apiVersionsToAPIGroup(apiVersions *unversioned.APIVersions) (apiGroup unver return } -func (d *DiscoveryClient) get(url string) (resp *http.Response, err error) { - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return nil, err - } - return d.httpClient.Do(req) -} - // 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 - url := d.baseURL - url.Path = "/api" - resp, err := d.get(url.String()) - if err != nil { + v := &unversioned.APIVersions{} + err = d.Get().AbsPath("/api").Do().Into(v) + apiGroup := unversioned.APIGroup{} + if err == nil { + apiGroup = apiVersionsToAPIGroup(v) + } + if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) { return nil, err } - var v unversioned.APIVersions - defer resp.Body.Close() - err = json.NewDecoder(resp.Body).Decode(&v) - if err != nil { - return nil, fmt.Errorf("unexpected error: %v", err) - } - apiGroup := apiVersionsToAPIGroup(&v) // Get the groupVersions exposed at /apis - url.Path = "/apis" - resp2, err := d.get(url.String()) - if err != nil { + apiGroupList = &unversioned.APIGroupList{} + err = d.Get().AbsPath("/apis").Do().Into(apiGroupList) + if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) { return nil, err } - defer resp2.Body.Close() - apiGroupList = &unversioned.APIGroupList{} - if err = json.NewDecoder(resp2.Body).Decode(&apiGroupList); err != nil { - return nil, fmt.Errorf("unexpected error: %v", 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 @@ -116,20 +101,21 @@ func (d *DiscoveryClient) ServerGroups() (apiGroupList *unversioned.APIGroupList // ServerResourcesForGroupVersion returns the supported resources for a group and version. func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (resources *unversioned.APIResourceList, err error) { - url := d.baseURL + url := url.URL{} if groupVersion == "v1" { url.Path = "/api/" + groupVersion } else { url.Path = "/apis/" + groupVersion } - resp, err := d.get(url.String()) - if err != nil { - return nil, err - } - defer resp.Body.Close() resources = &unversioned.APIResourceList{} - if err = json.NewDecoder(resp.Body).Decode(resources); err != nil { - return nil, fmt.Errorf("unexpected error: %v", err) + err = d.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 + } else { + return nil, err + } } return resources, nil } @@ -155,6 +141,8 @@ func (d *DiscoveryClient) ServerResources() (map[string]*unversioned.APIResource func setDiscoveryDefaults(config *Config) error { config.Prefix = "" config.Version = "" + // Discovery client deals with unversioned objects, so we use api.Codec. + config.Codec = api.Codec return nil } @@ -165,11 +153,6 @@ func NewDiscoveryClient(c *Config) (*DiscoveryClient, error) { if err := setDiscoveryDefaults(&config); err != nil { return nil, err } - transport, err := TransportFor(c) - if err != nil { - return nil, err - } - client := &http.Client{Transport: transport} - baseURL, err := defaultServerUrlFor(c) - return &DiscoveryClient{client, *baseURL}, nil + client, err := UnversionedRESTClientFor(&config) + return &DiscoveryClient{client}, err } diff --git a/pkg/client/unversioned/helper.go b/pkg/client/unversioned/helper.go index dfecf06fd2d..6534ca2398a 100644 --- a/pkg/client/unversioned/helper.go +++ b/pkg/client/unversioned/helper.go @@ -416,6 +416,31 @@ func RESTClientFor(config *Config) (*RESTClient, error) { return client, nil } +// UnversionedRESTClientFor is the same as RESTClientFor, except that it allows +// the config.Version to be empty. +func UnversionedRESTClientFor(config *Config) (*RESTClient, error) { + if config.Codec == nil { + return nil, fmt.Errorf("Codec is required when initializing a RESTClient") + } + + baseURL, err := defaultServerUrlFor(config) + if err != nil { + return nil, err + } + + client := NewRESTClient(baseURL, config.Version, config.Codec, config.QPS, config.Burst) + + transport, err := TransportFor(config) + if err != nil { + return nil, err + } + + if transport != http.DefaultTransport { + client.Client = &http.Client{Transport: transport} + } + return client, nil +} + var ( // tlsTransports stores reusable round trippers with custom TLSClientConfig options tlsTransports = map[string]*http.Transport{} diff --git a/pkg/conversion/decode.go b/pkg/conversion/decode.go index b6a5da5edfb..4ecf632c9d0 100644 --- a/pkg/conversion/decode.go +++ b/pkg/conversion/decode.go @@ -19,6 +19,7 @@ package conversion import ( "errors" "fmt" + "github.com/ugorji/go/codec" )